class Window(MWindow, Widget): """ The toolkit specific implementation of a Window. See the IWindow interface for the API documentation. """ #### 'IWindow' interface ################################################## position = Property(Tuple) size = Property(Tuple) title = Unicode #### Events ##### activated = Event closed = Event closing = Event deactivated = Event key_pressed = Event(KeyPressedEvent) opened = Event opening = Event #### Private interface #################################################### # Shadow trait for position. _position = Tuple((-1, -1)) # Shadow trait for size. _size = Tuple((-1, -1)) ########################################################################### # 'IWindow' interface. ########################################################################### def activate(self): self.control.Iconize(False) self.control.Raise() def show(self, visible): self.control.Show(visible) ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _add_event_listeners(self): self.control.Bind(wx.EVT_ACTIVATE, self._wx_on_activate) self.control.Bind(wx.EVT_SHOW, self._wx_on_show) self.control.Bind(wx.EVT_CLOSE, self._wx_on_close) self.control.Bind(wx.EVT_SIZE, self._wx_on_control_size) self.control.Bind(wx.EVT_MOVE, self._wx_on_control_move) self.control.Bind(wx.EVT_CHAR, self._wx_on_char) ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # create a basic window control style = wx.DEFAULT_FRAME_STYLE \ | wx.FRAME_NO_WINDOW_MENU \ | wx.CLIP_CHILDREN control = wx.Frame(parent, -1, self.title, style=style, size=self.size, pos=self.position) control.SetBackgroundColour(SystemMetrics().dialog_background_color) control.Enable(self.enabled) # XXX starting with self.visible true is generally a bad idea control.Show(self.visible) return control ########################################################################### # Private interface. ########################################################################### def _get_position(self): """ Property getter for position. """ return self._position def _set_position(self, position): """ Property setter for position. """ if self.control is not None: self.control.SetPosition(position) old = self._position self._position = position self.trait_property_changed('position', old, position) def _get_size(self): """ Property getter for size. """ return self._size def _set_size(self, size): """ Property setter for size. """ if self.control is not None: self.control.SetSize(size) old = self._size self._size = size self.trait_property_changed('size', old, size) def _title_changed(self, title): """ Static trait change handler. """ if self.control is not None: self.control.SetTitle(title) #### wx event handlers #################################################### def _wx_on_activate(self, event): """ Called when the frame is being activated or deactivated. """ if event.GetActive(): self.activated = self else: self.deactivated = self event.Skip() def _wx_on_show(self, event): """ Called when the frame is being activated or deactivated. """ self.visible = event.IsShown() event.Skip() def _wx_on_close(self, event): """ Called when the frame is being closed. """ self.close() def _wx_on_control_move(self, event): """ Called when the window is resized. """ # Get the real position and set the trait without performing # notification. # WXBUG - From the API documentation you would think that you could # call event.GetPosition directly, but that would be wrong. The pixel # reported by that call is the pixel just below the window menu and # just right of the Windows-drawn border. self._position = event.GetEventObject().GetPositionTuple() event.Skip() def _wx_on_control_size(self, event): """ Called when the window is resized. """ # Get the new size and set the shadow trait without performing # notification. wxsize = event.GetSize() self._size = (wxsize.GetWidth(), wxsize.GetHeight()) event.Skip() def _wx_on_char(self, event): """ Called when a key is pressed when the tree has focus. """ self.key_pressed = KeyPressedEvent( alt_down=event.m_altDown == 1, control_down=event.m_controlDown == 1, shift_down=event.m_shiftDown == 1, key_code=event.m_keyCode, event=event) event.Skip()
class PipelineBase(Base): """ Base class for all the Source, Filters and Modules in the pipeline. """ # The version of this class. Used for persistence. __version__ = 0 # A list of outputs for this object. outputs = List(record=False) # The actors generated by this object that will be rendered on the # scene. Changing this list while the actors are renderered *is* # safe and will do the right thing. actors = List(record=False) # The optional list of actors belonging to this object. These # will be added to the scene at an appropriate time. Changing # this list while the widgets are renderered *is* safe and will do # the right thing. widgets = List(record=False) # Information about what this object can consume. input_info = Instance(PipelineInfo) # Information about what this object can produce. output_info = Instance(PipelineInfo) ######################################## # Events. # This event is fired when the pipeline has changed. pipeline_changed = Event(record=False) # This event is fired when the data alone changes but the pipeline # outputs are the same. data_changed = Event(record=False) ################################################## # Private traits. ################################################## # Identifies if `actors` and `widgets` are added to the `scene` or # not. _actors_added = Bool(False) # Stores the state of the widgets prior to disabling them. _widget_state = List ###################################################################### # `object` interface. ###################################################################### def __get_pure_state__(self): d = super(PipelineBase, self).__get_pure_state__() # These are setup dynamically so we should not pickle them. for x in ('outputs', 'actors', 'widgets', '_actors_added', ): d.pop(x, None) return d ###################################################################### # `Base` interface ###################################################################### def start(self): """This is invoked when this object is added to the mayavi pipeline. Note that when start is invoked, all the other information for the pipeline should be already set. """ if self.running: return # Add any actors. self.add_actors() # Call parent method to set the running state. super(PipelineBase, self).start() def stop(self): """Invoked when this object is removed from the mayavi pipeline. """ if not self.running: return # Remove any of our actors from the scene. self.remove_actors() # Call parent method to set the running state. super(PipelineBase, self).stop() def render(self): """Invokes render on the scene, this in turn invokes Render on the VTK pipeline. """ s = self.scene if s is not None: s.render() elif self.running: # If there is no scene and we are running, we flush the # pipeline manually by calling update. for actor in self.actors: if hasattr(actor, 'mapper'): m = actor.mapper if m is not None: m.update() for widget in self.widgets: if hasattr(widget, 'input'): input = widget.input if input is not None: input.update() if hasattr(self, 'components'): for component in self.components: component.render() ###################################################################### # `PipelineBase` interface. ###################################################################### # Normally, you will not need to override the following methods # but you can if you really need to for whatever reason. def add_actors(self): """Adds `self.actors` to the scene. This is typically called when start is invoked. You should avoid calling this unless you know what you are doing. """ scene = self.scene if scene is not None: scene.add_actors(self.actors) scene.add_widgets(self.widgets) self._set_widget_visibility(self.widgets) self._actors_added = True def remove_actors(self): """Removes `self.actors` from the scene. This is typically called when stop is invoked. You should avoid calling this unless you know what you are doing. """ scene = self.scene if scene is not None: scene.remove_actors(self.actors) scene.remove_widgets(self.widgets) self._actors_added = False ###################################################################### # Non-public interface ###################################################################### def _outputs_changed(self, new): self.pipeline_changed = True def _outputs_items_changed(self, list_event): self.pipeline_changed = True def _actors_changed(self, old, new): if self._actors_added: self.scene.remove_actors(old) self.scene.add_actors(new) self.scene.render() def _actors_items_changed(self, list_event): if self._actors_added: self.scene.remove_actors(list_event.removed) self.scene.add_actors(list_event.added) self.scene.render() def _widgets_changed(self, old, new): self._handle_widgets_changed(old, new) def _widgets_items_changed(self, list_event): self._handle_widgets_changed(list_event.removed, list_event.added) def _handle_widgets_changed(self, removed, added): if self._actors_added: scene = self.scene scene.remove_widgets(removed) scene.add_widgets(added) self._set_widget_visibility(added) def _scene_changed(self, old_scene, new_scene): if self._actors_added: old_scene.remove_actors(self.actors) old_scene.remove_widgets(self.widgets) new_scene.add_actors(self.actors) new_scene.add_widgets(self.widgets) self._set_widget_visibility(self.widgets) def _backup_widget_state(self): # store the enabled trait of the widgets # in the _widget_state list state = [] for w in self.widgets: state.append(w.enabled) self._widget_state[:] = state def _restore_widget_state(self): if len(self._widget_state) != len(self.widgets): # someone has played with the widgets # we just enable all of them for w in self.widgets: w.enabled = True else: for i in range(len(self.widgets)): self.widgets[i].enabled = self._widget_state[i] def _visible_changed(self,value): if value: # restore the state of the widgets from the # backed up values. self._restore_widget_state() else: self._backup_widget_state() # disable all the widgets self._set_widget_visibility(self.widgets) # hide all actors for a in self.actors: a.visibility = value self.render() super(PipelineBase , self)._visible_changed(value) def _set_widget_visibility(self, widgets): if not self.visible: for widget in widgets: widget.enabled = False
class MEditorAreaPane(HasTraits): #### 'IEditorAreaPane' interface ########################################## active_editor = Instance(IEditor) editors = List(IEditor) file_drop_extensions = List(Str) file_dropped = Event(File) hide_tab_bar = Bool(False) #### Protected traits ##################################################### _factory_map = Dict(Callable, List(Callable)) ########################################################################### # 'IEditorAreaPane' interface. ########################################################################### def create_editor(self, obj, factory=None): """ Creates an editor for an object. """ if factory is None: factory = self.get_factory(obj) if factory is not None: return factory(editor_area=self, obj=obj) return None def edit(self, obj, factory=None, use_existing=True): """ Edit an object. """ if use_existing: # Is the object already being edited in the window? editor = self.get_editor(obj) if editor is not None: self.activate_editor(editor) return editor # If not, create an editor for it. editor = self.create_editor(obj, factory) if editor is None: logger.warn('Cannot create editor for obj %r', obj) else: self.add_editor(editor) self.activate_editor(editor) return editor def get_editor(self, obj): """ Returns the editor for an object. """ for editor in self.editors: if editor.obj == obj: return editor return None def get_factory(self, obj): """ Returns an editor factory suitable for editing an object. """ for factory, filters in self._factory_map.iteritems(): for filter in filters: # FIXME: We should swallow exceptions, but silently? try: if filter(obj): return factory except: pass return None def register_factory(self, factory, filter): """ Registers a factory for creating editors. """ self._factory_map.setdefault(factory, []).append(filter) def unregister_factory(self, factory): """ Unregisters a factory for creating editors. """ if factory in self._factory_map: del self._factory_map[factory]
class IPythonWidget(Widget): """ The toolkit specific implementation of a PythonShell. See the IPythonShell interface for the API documentation. """ # 'IPythonShell' interface --------------------------------------------- command_executed = Event() key_pressed = Event(KeyPressedEvent) # 'IPythonWidget' interface -------------------------------------------- interp = Instance(Interpreter, ()) # ------------------------------------------------------------------------ # 'object' interface. # ------------------------------------------------------------------------ # FIXME v3: Either make this API consistent with other Widget sub-classes # or make it a sub-class of HasTraits. def __init__(self, parent, **traits): """ Creates a new pager. """ # Base class constructor. super(IPythonWidget, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) # ------------------------------------------------------------------------ # 'IPythonShell' interface. # ------------------------------------------------------------------------ def interpreter(self): return self.interp def execute_command(self, command, hidden=True): self.control.execute_command(command, hidden=hidden) self.command_executed = True def execute_file(self, path, hidden=True): self.control.execute_command("%run " + '"%s"' % path, hidden=hidden) self.command_executed = True # ------------------------------------------------------------------------ # Protected 'IWidget' interface. # ------------------------------------------------------------------------ def _create_control(self, parent): # Create the controller based on the version of the installed IPython klass = IPythonController if IPYTHON_VERSION[0] == 0: if IPYTHON_VERSION[1] == 9: klass = IPython09Controller elif IPYTHON_VERSION[1] == 10: klass = IPython010Controller shell = klass(parent, -1, shell=self.interp) # Listen for key press events. shell.Bind(wx.EVT_CHAR, self._wx_on_char) # Enable the shell as a drag and drop target. shell.SetDropTarget(PythonDropTarget(self)) return shell # ------------------------------------------------------------------------ # 'PythonDropTarget' handler interface. # ------------------------------------------------------------------------ def on_drop(self, x, y, obj, default_drag_result): """ Called when a drop occurs on the shell. """ # If this is a file, we'll just print the file name if isinstance(obj, EnthoughtFile): self.control.write(obj.absolute_path) elif (isinstance(obj, list) and len(obj) == 1 and isinstance(obj[0], EnthoughtFile)): self.control.write(obj[0].absolute_path) else: # Not a file, we'll inject the object in the namespace # If we can't create a valid Python identifier for the name of an # object we use this instead. name = "dragged" if (hasattr(obj, "name") and isinstance(obj.name, str) and len(obj.name) > 0): py_name = python_name(obj.name) # Make sure that the name is actually a valid Python identifier. try: if eval(py_name, {py_name: True}): name = py_name except: pass self.interp.user_ns[name] = obj self.execute_command(name, hidden=False) self.control.SetFocus() # We always copy into the shell since we don't want the data # removed from the source return wx.DragCopy def on_drag_over(self, x, y, obj, default_drag_result): """ Always returns wx.DragCopy to indicate we will be doing a copy.""" return wx.DragCopy # ------------------------------------------------------------------------ # Private handler interface. # ------------------------------------------------------------------------ def _wx_on_char(self, event): """ Called whenever a change is made to the text of the document. """ self.key_pressed = KeyPressedEvent( alt_down=event.AltDown() == 1, control_down=event.ControlDown() == 1, shift_down=event.ShiftDown() == 1, key_code=event.GetKeyCode(), event=event, ) # Give other event handlers a chance. event.Skip()
class TVTKScene(HasPrivateTraits): """A TVTK interactor scene widget. This widget uses a RenderWindowInteractor and therefore supports interaction with VTK widgets. The widget uses TVTK. The widget also supports the following: - Save the scene to a bunch of common (and not so common) image formats. - save the rendered scene to the clipboard. - adding/removing lists/tuples of actors - setting the view to useful predefined views (just like in MayaVi). - If one passes `stereo=1` to the constructor, stereo rendering is enabled. By default this is disabled. Changing the stereo trait has no effect during runtime. - One can disable rendering by setting `disable_render` to True. """ # The version of this class. Used for persistence. __version__ = 0 ########################################################################### # Traits. ########################################################################### # Turn on/off stereo rendering. This is set on initialization and # has no effect once the widget is realized. stereo = Bool(False) # Perform line smoothing for all renderered lines. This produces # much nicer looking lines but renders slower. This setting works # only when called before the first render. line_smoothing = Bool(False) # Perform point smoothing for all renderered points. This # produces much nicer looking points but renders slower. This # setting works only when called before the first render. point_smoothing = Bool(False) # Perform polygon smoothing (anti-aliasing) for all rendered # polygons. This produces much nicer looking points but renders # slower. This setting works only when called before the first # render. polygon_smoothing = Bool(False) # Enable parallel projection. This trait is synchronized with # that of the camera. parallel_projection = Bool(False, desc='if the camera uses parallel projection') # Disable rendering. disable_render = Bool(False, desc='if rendering is to be disabled') # Enable off-screen rendering. This allows a user to render the # scene to an image without the need to have the window active. # For example, the application can be minimized and the saved # scene should be generated correctly. This is handy for batch # scripts and the like. This works under Win32. Under Mac OS X # and Linux it requires a recent VTK version (later than Oct 2005 # and ideally later than March 2006) to work correctly. off_screen_rendering = Bool(False, desc='if off-screen rendering is enabled') # The background color of the window. This is really a shadow # trait of the renderer's background. Delegation does not seem to # work nicely for this. background = Trait(vtk_color_trait((0.5, 0.5, 0.5)), desc='the background color of the window') # The default foreground color of any actors. This basically # saves the preference and actors will listen to changes -- # the scene itself does not use this. foreground = Trait(vtk_color_trait((1.0, 1.0, 1.0)), desc='the default foreground color of actors') # The magnification to use when generating images from the render # window. magnification = Range( 1, 2048, 1, desc='the magnification used when the screen is saved to an image') # Specifies the number of frames to use for anti-aliasing when # saving a scene. This basically increases # `self.render_window.aa_frames` in order to produce anti-aliased # figures when a scene is saved to an image. It then restores the # `aa_frames` in order to get interactive rendering rates. anti_aliasing_frames = Range( 0, 20, 8, desc='number of frames to use for anti-aliasing when saving a scene') # Default JPEG quality. jpeg_quality = Range(10, 100, 95, desc='the quality of the JPEG image to produce') # Default JPEG progressive setting. jpeg_progressive = Bool(True, desc='if the generated JPEG should be progressive') # The light manager. light_manager = Instance(light_manager.LightManager, record=True) # Is the scene busy or not. busy = Property(Bool, record=False) ######################################## # Events # Lifecycle events: there are no opening/opened events since the # control is actually created in __init__. # The control is going to be closed. closing = Event(record=False) # The control has been closed. closed = Event(record=False) # Event fired when an actor is added to the scene. actor_added = Event(record=False) # Event fired when any actor is removed from the scene. actor_removed = Event(record=False) ######################################## # Properties. # The interactor used by the scene. interactor = Property(Instance(tvtk.GenericRenderWindowInteractor)) # The render_window. render_window = Property(Instance(tvtk.RenderWindow)) # The renderer. renderer = Property(Instance(tvtk.Renderer)) # The camera. camera = Property(Instance(tvtk.Camera)) # The control to mimic the Widget behavior. control = Any ######################################## # Private traits. # A recorder for script recording. recorder = Instance(HasTraits, record=False, transient=True) # Cached last camera state. _last_camera_state = Any(transient=True) _camera_observer_id = Int(transient=True) _script_id = Str(transient=True) # The renderer instance. _renderer = Instance(tvtk.Renderer) _renwin = Instance(tvtk.RenderWindow) _interactor = Instance(tvtk.RenderWindowInteractor) _camera = Instance(tvtk.Camera) _busy_count = Int(0) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent=None, **traits): """ Initializes the object. """ # Base class constructor. super(TVTKScene, self).__init__(**traits) # Used to set the view of the scene. self._def_pos = 1 self.control = self._create_control(parent) self._renwin.update_traits() def __get_pure_state__(self): """Allows us to pickle the scene.""" # The control attribute is not picklable since it is a VTK # object so we remove it. d = self.__dict__.copy() for x in [ 'control', '_renwin', '_interactor', '_camera', '_busy_count', '__sync_trait__', 'recorder', '_last_camera_state', '_camera_observer_id', '_script_id', '__traits_listener__' ]: d.pop(x, None) # Additionally pickle these. d['camera'] = self.camera return d def __getstate__(self): return state_pickler.dumps(self) def __setstate__(self, str_state): # This method is unnecessary since this object will almost # never be pickled by itself and only via an object that # contains it, therefore __init__ will be called when the # scene is constructed. However, setstate is defined just for # completeness. state_pickler.set_state(self, state_pickler.loads_state(str_state)) ########################################################################### # 'Scene' interface. ########################################################################### def render(self): """ Force the scene to be rendered. Nothing is done if the `disable_render` trait is set to True.""" if not self.disable_render: self._renwin.render() def add_actors(self, actors): """ Adds a single actor or a tuple or list of actors to the renderer.""" # Reset the zoom if this is the first actor. reset_zoom = (len(self._renderer.actors) == 0 and len(self._renderer.volumes) == 0) if hasattr(actors, '__iter__'): for actor in actors: self._renderer.add_actor(actor) else: self._renderer.add_actor(actors) self.actor_added = actors if reset_zoom: self.reset_zoom() else: self.render() def remove_actors(self, actors): """ Removes a single actor or a tuple or list of actors from the renderer.""" if hasattr(actors, '__iter__'): for actor in actors: self._renderer.remove_actor(actor) else: self._renderer.remove_actor(actors) self.actor_removed = actors self.render() # Conevenience methods. add_actor = add_actors remove_actor = remove_actors def add_widgets(self, widgets, enabled=True): """Adds a single 3D widget or a sequence of widgets to the renderer. If `enabled` is True the widget is also enabled once it is added.""" if not hasattr(widgets, '__iter__'): widgets = [widgets] iren = self._interactor for widget in widgets: widget.interactor = iren widget.enabled = enabled self.render() def remove_widgets(self, widgets): """Removes a single 3D widget or a sequence of widgets from the renderer.""" if not hasattr(widgets, '__iter__'): widgets = [widgets] iren = self._interactor for widget in widgets: if widget.interactor is not None: widget.enabled = False widget.interactor = None self.render() def close(self): """Close the scene cleanly. This ensures that the scene is shutdown cleanly. This should be called if you are getting async errors when closing a scene from a UI. This is based on the observations of Charl Botha here: http://public.kitware.com/pipermail/vtkusers/2008-May/095291.html """ # Return if we are already closed. if self._renwin is None: return # Fire the "closing" event. self.closing = True # Disable any renders through traits listner callbacks. self.disable_render = True # Remove sync trait listeners. self.sync_trait('background', self._renderer, remove=True) self.sync_trait('parallel_projection', self.camera, remove=True) self.sync_trait('off_screen_rendering', self._renwin, remove=True) # Remove all the renderer's props. self._renderer.remove_all_view_props() # Set the renderwindow to release all resources and the OpenGL # context. self._renwin.finalize() # Disconnect the interactor from the renderwindow. self._interactor.render_window = None # Remove the reference to the render window. del self._renwin # Fire the "closed" event. self.closed = True def x_plus_view(self): """View scene down the +X axis. """ self._update_view(self._def_pos, 0, 0, 0, 0, 1) self._record_methods('x_plus_view()') def x_minus_view(self): """View scene down the -X axis. """ self._update_view(-self._def_pos, 0, 0, 0, 0, 1) self._record_methods('x_minus_view()') def z_plus_view(self): """View scene down the +Z axis. """ self._update_view(0, 0, self._def_pos, 0, 1, 0) self._record_methods('z_plus_view()') def z_minus_view(self): """View scene down the -Z axis. """ self._update_view(0, 0, -self._def_pos, 0, 1, 0) self._record_methods('z_minus_view()') def y_plus_view(self): """View scene down the +Y axis. """ self._update_view(0, self._def_pos, 0, 1, 0, 0) self._record_methods('y_plus_view()') def y_minus_view(self): """View scene down the -Y axis. """ self._update_view(0, -self._def_pos, 0, 1, 0, 0) self._record_methods('y_minus_view()') def isometric_view(self): """Set the view to an iso-metric view. """ self._update_view(self._def_pos, self._def_pos, self._def_pos, 0, 0, 1) self._record_methods('isometric_view()') def reset_zoom(self): """Reset the camera so everything in the scene fits.""" self._renderer.reset_camera() self.render() self._record_methods('reset_zoom()') def save(self, file_name, size=None, **kw_args): """Saves rendered scene to one of several image formats depending on the specified extension of the filename. If an additional size (2-tuple) argument is passed the window is resized to the specified size in order to produce a suitably sized output image. Please note that when the window is resized, the window may be obscured by other widgets and the camera zoom is not reset which is likely to produce an image that does not reflect what is seen on screen. Any extra keyword arguments are passed along to the respective image format's save method. """ ext = os.path.splitext(file_name)[1] meth_map = { '.ps': 'ps', '.bmp': 'bmp', '.tiff': 'tiff', '.png': 'png', '.jpg': 'jpg', '.jpeg': 'jpg', '.iv': 'iv', '.wrl': 'vrml', '.vrml': 'vrml', '.oogl': 'oogl', '.rib': 'rib', '.obj': 'wavefront', '.eps': 'gl2ps', '.pdf': 'gl2ps', '.tex': 'gl2ps', '.x3d': 'x3d', '.pov': 'povray' } if ext.lower() not in meth_map.keys(): raise ValueError, \ 'Unable to find suitable image type for given file extension.' meth = getattr(self, 'save_' + meth_map[ext]) if size is not None: orig_size = self.get_size() self.set_size(size) meth(file_name, **kw_args) self.set_size(orig_size) self._record_methods('save(%r, %r)' % (file_name, size)) else: meth(file_name, **kw_args) self._record_methods('save(%r)' % (file_name)) def save_ps(self, file_name): """Saves the rendered scene to a rasterized PostScript image. For vector graphics use the save_gl2ps method.""" if len(file_name) != 0: w2if = tvtk.WindowToImageFilter( read_front_buffer=not self.off_screen_rendering) w2if.magnification = self.magnification self._lift() w2if.input = self._renwin ex = tvtk.PostScriptWriter() ex.file_name = file_name configure_input(ex, w2if) self._exporter_write(ex) def save_bmp(self, file_name): """Save to a BMP image file.""" if len(file_name) != 0: w2if = tvtk.WindowToImageFilter( read_front_buffer=not self.off_screen_rendering) w2if.magnification = self.magnification self._lift() w2if.input = self._renwin ex = tvtk.BMPWriter() ex.file_name = file_name configure_input(ex, w2if) self._exporter_write(ex) def save_tiff(self, file_name): """Save to a TIFF image file.""" if len(file_name) != 0: w2if = tvtk.WindowToImageFilter( read_front_buffer=not self.off_screen_rendering) w2if.magnification = self.magnification self._lift() w2if.input = self._renwin ex = tvtk.TIFFWriter() ex.file_name = file_name configure_input(ex, w2if) self._exporter_write(ex) def save_png(self, file_name): """Save to a PNG image file.""" if len(file_name) != 0: w2if = tvtk.WindowToImageFilter( read_front_buffer=not self.off_screen_rendering) w2if.magnification = self.magnification self._lift() w2if.input = self._renwin ex = tvtk.PNGWriter() ex.file_name = file_name configure_input(ex, w2if) self._exporter_write(ex) def save_jpg(self, file_name, quality=None, progressive=None): """Arguments: file_name if passed will be used, quality is the quality of the JPEG(10-100) are valid, the progressive arguments toggles progressive jpegs.""" if len(file_name) != 0: if not quality and not progressive: quality, progressive = self.jpeg_quality, self.jpeg_progressive w2if = tvtk.WindowToImageFilter( read_front_buffer=not self.off_screen_rendering) w2if.magnification = self.magnification self._lift() w2if.input = self._renwin ex = tvtk.JPEGWriter() ex.quality = quality ex.progressive = progressive ex.file_name = file_name configure_input(ex, w2if) self._exporter_write(ex) def save_iv(self, file_name): """Save to an OpenInventor file.""" if len(file_name) != 0: ex = tvtk.IVExporter() self._lift() ex.input = self._renwin ex.file_name = file_name self._exporter_write(ex) def save_vrml(self, file_name): """Save to a VRML file.""" if len(file_name) != 0: ex = tvtk.VRMLExporter() self._lift() ex.input = self._renwin ex.file_name = file_name self._exporter_write(ex) def save_oogl(self, file_name): """Saves the scene to a Geomview OOGL file. Requires VTK 4 to work.""" if len(file_name) != 0: ex = tvtk.OOGLExporter() self._lift() ex.input = self._renwin ex.file_name = file_name self._exporter_write(ex) def save_rib(self, file_name, bg=0, resolution=None, resfactor=1.0): """Save scene to a RenderMan RIB file. Keyword Arguments: file_name -- File name to save to. bg -- Optional background option. If 0 then no background is saved. If non-None then a background is saved. If left alone (defaults to None) it will result in a pop-up window asking for yes/no. resolution -- Specify the resolution of the generated image in the form of a tuple (nx, ny). resfactor -- The resolution factor which scales the resolution. """ if resolution == None: # get present window size Nx, Ny = self.render_window.size else: try: Nx, Ny = resolution except TypeError: raise TypeError, \ "Resolution (%s) should be a sequence with two elements"%resolution if len(file_name) == 0: return f_pref = os.path.splitext(file_name)[0] ex = tvtk.RIBExporter() ex.size = int(resfactor * Nx), int(resfactor * Ny) ex.file_prefix = f_pref ex.texture_prefix = f_pref + "_tex" self._lift() ex.render_window = self._renwin ex.background = bg if VTK_VER[:3] in ['4.2', '4.4']: # The vtkRIBExporter is broken in respect to VTK light # types. Therefore we need to convert all lights into # scene lights before the save and later convert them # back. ######################################## # Internal functions def x3to4(x): # convert 3-vector to 4-vector (w=1 -> point in space) return (x[0], x[1], x[2], 1.0) def x4to3(x): # convert 4-vector to 3-vector return (x[0], x[1], x[2]) def cameralight_transform(light, xform, light_type): # transform light by 4x4 matrix xform origin = x3to4(light.position) focus = x3to4(light.focal_point) neworigin = xform.multiply_point(origin) newfocus = xform.multiply_point(focus) light.position = x4to3(neworigin) light.focal_point = x4to3(newfocus) light.light_type = light_type ######################################## save_lights_type = [] for light in self.light_manager.lights: save_lights_type.append(light.source.light_type) # Convert lights to scene lights. cam = self.camera xform = tvtk.Matrix4x4() xform.deep_copy(cam.camera_light_transform_matrix) for light in self.light_manager.lights: cameralight_transform(light.source, xform, "scene_light") # Write the RIB file. self._exporter_write(ex) # Now re-convert lights to camera lights. xform.invert() for i, light in enumerate(self.light_manager.lights): cameralight_transform(light.source, xform, save_lights_type[i]) # Change the camera position. Otherwise VTK would render # one broken frame after the export. cam.roll(0.5) cam.roll(-0.5) else: self._exporter_write(ex) def save_wavefront(self, file_name): """Save scene to a Wavefront OBJ file. Two files are generated. One with a .obj extension and another with a .mtl extension which contains the material proerties. Keyword Arguments: file_name -- File name to save to """ if len(file_name) != 0: ex = tvtk.OBJExporter() self._lift() ex.input = self._renwin f_pref = os.path.splitext(file_name)[0] ex.file_prefix = f_pref self._exporter_write(ex) def save_gl2ps(self, file_name, exp=None): """Save scene to a vector PostScript/EPS/PDF/TeX file using GL2PS. If you choose to use a TeX file then note that only the text output is saved to the file. You will need to save the graphics separately. Keyword Arguments: file_name -- File name to save to. exp -- Optionally configured vtkGL2PSExporter object. Defaults to None and this will use the default settings with the output file type chosen based on the extention of the file name. """ # Make sure the exporter is available. if not hasattr(tvtk, 'GL2PSExporter'): msg = "Saving as a vector PS/EPS/PDF/TeX file using GL2PS is "\ "either not supported by your version of VTK or "\ "you have not configured VTK to work with GL2PS -- read "\ "the documentation for the vtkGL2PSExporter class." print msg return if len(file_name) != 0: f_prefix, f_ext = os.path.splitext(file_name) ex = None if exp: ex = exp if not isinstance(exp, tvtk.GL2PSExporter): msg = "Need a vtkGL2PSExporter you passed a "\ "%s"%exp.__class__.__name__ raise TypeError, msg ex.file_prefix = f_prefix else: ex = tvtk.GL2PSExporter() # defaults ex.file_prefix = f_prefix if f_ext == ".ps": ex.file_format = 'ps' elif f_ext == ".tex": ex.file_format = 'tex' elif f_ext == ".pdf": ex.file_format = 'pdf' else: ex.file_format = 'eps' ex.sort = 'bsp' ex.compress = 1 ex.edit_traits(kind='livemodal') self._lift() ex.render_window = self._renwin if ex.write3d_props_as_raster_image: self._exporter_write(ex) else: ex.write() def save_x3d(self, file_name): """Save scene to an X3D file (http://www.web3d.org/x3d/). Keyword Arguments: file_name -- File name to save to. """ # Make sure the exporter is available. if not hasattr(tvtk, 'X3DExporter'): msg = "Saving as a X3D file does not appear to be "\ "supported by your version of VTK." print msg return if len(file_name) != 0: ex = tvtk.X3DExporter() ex.input = self._renwin ex.file_name = file_name ex.update() ex.write() def save_povray(self, file_name): """Save scene to a POVRAY (Persistance of Vision Raytracer), file (http://www.povray.org). Keyword Arguments: file_name -- File name to save to. """ # Make sure the exporter is available. if not hasattr(tvtk, 'POVExporter'): msg = "Saving as a POVRAY file does not appear to be "\ "supported by your version of VTK." print msg return if len(file_name) != 0: ex = tvtk.POVExporter() ex.input = self._renwin if hasattr(ex, 'file_name'): ex.file_name = file_name else: ex.file_prefix = os.path.splitext(file_name)[0] ex.update() ex.write() def get_size(self): """Return size of the render window.""" return self._interactor.size def set_size(self, size): """Set the size of the window.""" self._interactor.size = size self._renwin.size = size ########################################################################### # Properties. ########################################################################### def _get_interactor(self): """Returns the vtkRenderWindowInteractor of the parent class""" return self._interactor def _get_render_window(self): """Returns the scene's render window.""" return self._renwin def _get_renderer(self): """Returns the scene's renderer.""" return self._renderer def _get_camera(self): """ Returns the active camera. """ return self._renderer.active_camera def _get_busy(self): return self._busy_count > 0 def _set_busy(self, value): """The `busy` trait is either `True` or `False`. However, this could be problematic since we could have two methods `foo` and `bar that both set `scene.busy = True`. As soon as `bar` is done it sets `busy` back to `False`. This is wrong since the UI is still busy as `foo` is not done yet. We therefore store the number of busy calls and either increment it or decrement it and change the state back to `False` only when the count is zero. """ bc = self._busy_count if value: bc += 1 else: bc -= 1 bc = max(0, bc) self._busy_count = bc if bc == 1: self.trait_property_changed('busy', False, True) if bc == 0: self.trait_property_changed('busy', True, False) ########################################################################### # Non-public interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ # Create the renderwindow. renwin = self._renwin = tvtk.RenderWindow() # If we are doing offscreen rendering we set the window size to # (1,1) so the window does not appear at all if self.off_screen_rendering: renwin.size = (1, 1) renwin.set(point_smoothing=self.point_smoothing, line_smoothing=self.line_smoothing, polygon_smoothing=self.polygon_smoothing) # Create a renderer and add it to the renderwindow self._renderer = tvtk.Renderer() renwin.add_renderer(self._renderer) self._interactor = tvtk.RenderWindowInteractor(render_window=renwin) # Save a reference to our camera so it is not GC'd -- needed for # the sync_traits to work. self._camera = self.camera # Sync various traits. self._renderer.background = self.background self.sync_trait('background', self._renderer) self._renderer.on_trait_change(self.render, 'background') self._camera.parallel_projection = self.parallel_projection self.sync_trait('parallel_projection', self._camera) renwin.off_screen_rendering = self.off_screen_rendering self.sync_trait('off_screen_rendering', self._renwin) self.render_window.on_trait_change(self.render, 'off_screen_rendering') self.render_window.on_trait_change(self.render, 'stereo_render') self.render_window.on_trait_change(self.render, 'stereo_type') self.camera.on_trait_change(self.render, 'parallel_projection') self._interactor.initialize() self._interactor.render() self.light_manager = light_manager.LightManager(self) if self.off_screen_rendering: # We want the default size to be the normal (300, 300). # Setting the size now should not resize the window if # offscreen is working properly in VTK. renwin.size = (300, 300) return self._interactor def _lift(self): """Lift the window to the top. Useful when saving screen to an image.""" return def _exporter_write(self, ex): """Abstracts the exporter's write method.""" # Bumps up the anti-aliasing frames when the image is saved so # that the saved picture looks nicer. rw = self.render_window aa_frames = rw.aa_frames rw.aa_frames = self.anti_aliasing_frames rw.render() ex.update() ex.write() # Set the frames back to original setting. rw.aa_frames = aa_frames rw.render() def _update_view(self, x, y, z, vx, vy, vz): """Used internally to set the view.""" camera = self.camera camera.focal_point = 0.0, 0.0, 0.0 camera.position = x, y, z camera.view_up = vx, vy, vz self._renderer.reset_camera() self.render() def _disable_render_changed(self, val): if not val and self._renwin is not None: self.render() def _record_methods(self, calls): """A method to record a simple method called on self. We need a more powerful and less intrusive way like decorators to do this. Note that calls can be a string with new lines in which case we interpret this as multiple calls. """ r = self.recorder if r is not None: sid = self._script_id for call in calls.split('\n'): r.record('%s.%s' % (sid, call)) def _record_camera_position(self, vtk_obj=None, event=None): """Callback to record the camera position.""" r = self.recorder if r is not None: state = self._get_camera_state() lcs = self._last_camera_state if state != lcs: self._last_camera_state = state sid = self._script_id for key, value in state: r.record('%s.camera.%s = %r' % (sid, key, value)) r.record('%s.camera.compute_view_plane_normal()' % sid) r.record('%s.render()' % sid) def _get_camera_state(self): c = self.camera state = [] state.append(('position', list(c.position))) state.append(('focal_point', list(c.focal_point))) state.append(('view_angle', c.view_angle)) state.append(('view_up', list(c.view_up))) state.append(('clipping_range', list(c.clipping_range))) return state def _recorder_changed(self, r): """When the recorder is set we add an event handler so we can record the change to the camera position after the interaction. """ iren = self._interactor if r is not None: self._script_id = r.get_script_id(self) id = iren.add_observer('EndInteractionEvent', messenger.send) self._camera_observer_id = id i_vtk = tvtk.to_vtk(iren) messenger.connect(i_vtk, 'EndInteractionEvent', self._record_camera_position) else: self._script_id = '' iren.remove_observer(self._camera_observer_id) i_vtk = tvtk.to_vtk(iren) messenger.disconnect(i_vtk, 'EndInteractionEvent', self._record_camera_position)
class TableViewer(ContentViewer): """ A viewer for tabular data. """ # The content provider provides the actual table data. content_provider = Instance(TableContentProvider) # The label provider provides, err, the labels for the items in the table # (a label can have text and/or an image). label_provider = Instance(TableLabelProvider, ()) # The column provider provides information about the columns in the table # (column headers, width etc). column_provider = Instance(TableColumnProvider, ()) # The colours used to render odd and even numbered rows. even_row_background = Color("white") odd_row_background = Color((245, 245, 255)) # A row has been selected. row_selected = Event() # A row has been activated. row_activated = Event() # A drag operation was started on a node. row_begin_drag = Event() # The size of the icons in the table. _image_size = Tuple(Int, Int) def __init__(self, parent, image_size=(16, 16), **traits): """ Creates a new table viewer. 'parent' is the toolkit-specific control that is the table's parent. 'image_size' is a tuple in the form (int width, int height) that specifies the size of the images (if any) displayed in the table. """ create = traits.pop('create', True) # Base class constructors. super().__init__(parent=parent, _image_size=image_size, **traits) if create: self.create() warnings.warn( "automatic widget creation is deprecated and will be removed " "in a future Pyface version, use create=False and explicitly " "call create() for future behaviour", PendingDeprecationWarning, ) def _create_control(self, parent): # Create the toolkit-specific control. self.control = table = _Table(parent, self._image_size, self) # Table events. table.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_item_selected) table.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_item_activated) table.Bind(wx.EVT_LIST_BEGIN_DRAG, self._on_list_begin_drag) table.Bind(wx.EVT_LIST_BEGIN_RDRAG, self._on_list_begin_rdrag) table.Bind( wx.EVT_LIST_BEGIN_LABEL_EDIT, self._on_list_begin_label_edit ) table.Bind(wx.EVT_LIST_END_LABEL_EDIT, self._on_list_end_label_edit) # fixme: Bug[732104] indicates that this event does not get fired # in a virtual list control (it *does* get fired in a regular list # control 8^(). table.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._on_item_deselected) # Create the widget! self._create_widget(parent) # We use a dynamic handler instead of a static handler here, as we # don't want to react if the input is set in the constructor. self.observe(self._on_input_changed, "input") return table # ------------------------------------------------------------------------ # 'TableViewer' interface. # ------------------------------------------------------------------------ def select_row(self, row): """ Select the specified row. """ self.control.SetItemState( row, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED ) self.control.SetItemState( row, wx.LIST_STATE_FOCUSED, wx.LIST_STATE_FOCUSED ) # Make sure that the selected row is visible. fudge = max(0, row - 5) self.EnsureVisible(fudge) # Trait event notification. self.row_selected = row return # ------------------------------------------------------------------------ # Trait event handlers. # ------------------------------------------------------------------------ def _on_input_changed(self, event): """ Called when the input is changed. """ # Update the table contents. self._update_contents() if event.old is None: self._update_column_widths() return # ------------------------------------------------------------------------ # wx event handlers. # ------------------------------------------------------------------------ def _on_item_selected(self, event): """ Called when an item in the list is selected. """ # Get the index of the row that was selected (nice wx interface huh?!). row = event.Index # Trait event notification. self.row_selected = row return # fixme: Bug[732104] indicates that this event does not get fired in a # virtual list control (it *does* get fired in a regular list control 8^(). def _on_item_deselected(self, event): """ Called when an item in the list is selected. """ # Trait event notification. self.row_selected = -1 def _on_item_activated(self, event): """ Called when an item in the list is activated. """ # Get the index of the row that was activated (nice wx interface!). row = event.Index # Trait event notification. self.row_activated = row def _on_list_begin_drag(self, event=None, is_rdrag=False): """ Called when a drag operation is starting on a list item. """ # Trait notification. self.row_begin_drag = event.GetIndex() def _on_list_begin_rdrag(self, event=None): """ Called when a drag operation is starting on a list item. """ self._on_list_begin_drag(event, True) def _on_list_begin_label_edit(self, event=None): """ Called when a label edit is started. """ event.Veto() def _on_list_end_label_edit(self, event=None): """ Called when a label edit is completed. """ return # ------------------------------------------------------------------------ # Private interface. # ------------------------------------------------------------------------ FORMAT_MAP = { "left": wx.LIST_FORMAT_LEFT, "right": wx.LIST_FORMAT_RIGHT, "center": wx.LIST_FORMAT_CENTRE, "centre": wx.LIST_FORMAT_CENTRE, } def _create_widget(self, parent): """ Creates the widget. """ # Set up a default list item descriptor. info = wx.ListItem() info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT # Set the column headers. for index in range(self.column_provider.column_count): # Header text. info.m_text = self.column_provider.get_label(self, index) # Alignment of header text AND ALL cells in the column. alignment = self.column_provider.get_alignment(self, index) info.m_format = self.FORMAT_MAP.get(alignment, wx.LIST_FORMAT_LEFT) self.control.InsertColumn(index, info) # # Update the table contents and the column widths. self._update_contents() self._update_column_widths() def _update_contents(self): """ Updates the table content. """ self._elements = [] if self.input is not None: # Filtering... for element in self.content_provider.get_elements(self.input): for filter in self.filters: if not filter.select(self, self.input, element): break else: self._elements.append(element) # Sorting... if self.sorter is not None: self.sorter.sort(self, self.input, self._elements) # Setting this causes a refresh! self.control.SetItemCount(len(self._elements)) def _update_column_widths(self): """ Updates the column widths. """ # Set all columns to be the size of their largest item, or the size of # their header whichever is the larger. for column in range(self.control.GetColumnCount()): width = self.column_provider.get_width(self, column) if width == -1: width = self._get_column_width(column) self.control.SetColumnWidth(column, width) def _get_column_width(self, column): """ Return an appropriate width for the specified column. """ self.control.SetColumnWidth(column, wx.LIST_AUTOSIZE_USEHEADER) header_width = self.control.GetColumnWidth(column) if self.control.GetItemCount() == 0: width = header_width else: self.control.SetColumnWidth(column, wx.LIST_AUTOSIZE) data_width = self.control.GetColumnWidth(column) width = max(header_width, data_width) return width
class ListBox(LayoutWidget): """ A simple list box widget with a model-view architecture. """ # The model that provides the data for the list box. model = Instance(ListBoxModel) # The objects currently selected in the list. selection = Int(-1) # Events. # An item has been activated. item_activated = Event() # Default style. STYLE = wx.LB_SINGLE | wx.LB_HSCROLL | wx.LB_NEEDED_SB def __init__(self, parent=None, **traits): """ Creates a new list box. """ create = traits.pop('create', True) # Base-class constructors. super().__init__(parent=parent, **traits) # Create the widget! if create: self.create() warnings.warn( "automatic widget creation is deprecated and will be removed " "in a future Pyface version, use create=False and explicitly " "call create() for future behaviour", PendingDeprecationWarning, ) def _create(self): super()._create() self._populate() # Listen for changes to the model. self.model.observe(self._on_model_changed, "list_changed") def dispose(self): self.model.observe( self._on_model_changed, "list_changed", remove=True ) self.model.dispose() # ------------------------------------------------------------------------ # 'ListBox' interface. # ------------------------------------------------------------------------ def refresh(self): """ Refreshes the list box. """ # For now we just clear out the entire list. self.control.Clear() # Populate the list. self._populate() # ------------------------------------------------------------------------ # wx event handlers. # ------------------------------------------------------------------------ def _on_item_selected(self, event): """ Called when an item in the list is selected. """ listbox = event.GetEventObject() self.selection = listbox.GetSelection() def _on_item_activated(self, event): """ Called when an item in the list is activated. """ listbox = event.GetEventObject() index = listbox.GetSelection() # Trait event notification. self.item_activated = index # ------------------------------------------------------------------------ # Trait handlers. # ------------------------------------------------------------------------ # Static --------------------------------------------------------------- def _selection_changed(self, index): """ Called when the selected item is changed. """ if index != -1: self.control.SetSelection(index) # Dynamic -------------------------------------------------------------# def _on_model_changed(self, event): """ Called when the model has changed. """ # For now we just clear out the entire list. self.refresh() # ------------------------------------------------------------------------ # Private interface. # ------------------------------------------------------------------------ def _create_control(self, parent): """ Creates the widget. """ control = wx.ListBox(parent, -1, style=self.STYLE) # Wire it up! control.Bind( wx.EVT_LISTBOX, self._on_item_selected, id=self.control.GetId() ) control.Bind( wx.EVT_LISTBOX_DCLICK, self._on_item_activated, id=self.control.GetId(), ) # Populate the list. return control def _populate(self): """ Populates the list box. """ for index in range(self.model.get_item_count()): label, item = self.model.get_item_at(index) self.control.Append(label, item)
class CustomEditor(SimpleTextEditor): """Custom style of file editor, consisting of a file system tree view.""" #: Is the file editor scrollable? This value overrides the default. scrollable = True #: Wildcard filter to apply to the file dialog: filter = filter_trait #: Event fired when the file system view should be rebuilt: reload = Event() #: Event fired when the user double-clicks a file: dclick = Event() def init(self, parent): """Finishes initializing the editor by creating the underlying toolkit widget. """ style = self.get_style() factory = self.factory if (len(factory.filter) > 0) or (factory.filter_name != ""): style |= wx.DIRCTRL_SHOW_FILTERS self.control = wx.GenericDirCtrl(parent, style=style) self._tree = tree = self.control.GetTreeCtrl() id = tree.GetId() tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.update_object, id=id) tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_dclick, id=id) tree.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tooltip, id=id) self.filter = factory.filter self.sync_value(factory.filter_name, "filter", "from", is_list=True) self.sync_value(factory.reload_name, "reload", "from") self.sync_value(factory.dclick_name, "dclick", "to") self.set_tooltip() def dispose(self): """Disposes of the contents of an editor.""" tree, self._tree = self._tree, None tree.Unbind(wx.EVT_TREE_SEL_CHANGED) tree.Unbind(wx.EVT_TREE_ITEM_ACTIVATED) super().dispose() def update_object(self, event): """Handles the user changing the contents of the edit control.""" if self.control is not None: path = self.control.GetPath() if self.factory.allow_dir or isfile(path): if self.factory.truncate_ext: path = splitext(path)[0] self.value = path def update_editor(self): """Updates the editor when the object trait changes externally to the editor. """ if exists(self.str_value): self.control.SetPath(self.str_value) def get_style(self): """Returns the basic style to use for the control.""" return wx.DIRCTRL_EDIT_LABELS def get_error_control(self): """Returns the editor's control for indicating error status.""" return self._tree def _filter_changed(self): """Handles the 'filter' trait being changed.""" self.control.SetFilter("|".join(self.filter[:])) def _on_dclick(self, event): """Handles the user double-clicking on a file name.""" self.dclick = self.control.GetPath() def _on_tooltip(self, event): """Handles the user hovering on a file name for a tooltip.""" text = self._tree.GetItemText(event.GetItem()) event.SetToolTip(text) def _reload_changed(self): """Handles the 'reload' trait being changed.""" self.control.ReCreateTree()
class TabularEditor(Editor): """ A traits UI editor for editing tabular data (arrays, list of tuples, lists of objects, etc). """ # -- Trait Definitions ---------------------------------------------------- #: The event fired when a table update is needed: update = Event #: The event fired when a simple repaint is needed: refresh = Event #: The current set of selected items (which one is used depends upon the #: initial state of the editor factory 'multi_select' trait): selected = Any multi_selected = List #: The current set of selected item indices (which one is used depends upon #: the initial state of the editor factory 'multi_select' trait): selected_row = Int(-1) multi_selected_rows = List(Int) #: The optional extended name of the trait to synchronize the selection #: column with: selected_column = Int(-1) #: The most recently actived item and its index: activated = Any(comparison_mode=NO_COMPARE) activated_row = Int(comparison_mode=NO_COMPARE) #: The most recent left click data: clicked = Instance("TabularEditorEvent") #: The most recent left double click data: dclicked = Instance("TabularEditorEvent") #: The most recent right click data: right_clicked = Instance("TabularEditorEvent") #: The most recent right double click data: right_dclicked = Instance("TabularEditorEvent") #: The most recent column click data: column_clicked = Instance("TabularEditorEvent") #: The most recent column click data: column_right_clicked = Instance("TabularEditorEvent") #: The event triggering scrolling. scroll_to_row = Event(Int) #: The event triggering scrolling. scroll_to_column = Event(Int) #: Is the tabular editor scrollable? This value overrides the default. scrollable = True #: NIT: This doesn't seem to be used anywhere...can I delete? #: # Row index of item to select after rebuilding editor list: #: row = Any #: Should the selected item be edited after rebuilding the editor list: edit = Bool(False) #: The adapter from trait values to editor values: adapter = Instance(TabularAdapter) #: The table model associated with the editor: model = Instance(TabularModel) #: Dictionary mapping image names to QIcons images = Any({}) #: Dictionary mapping ImageResource objects to QIcons image_resources = Any({}) #: An image being converted: image = Image header_event_filter = Any() widget_factory = Callable(lambda *args, **kwds: _TableView(*args, **kwds)) # ------------------------------------------------------------------------- # Editor interface: # ------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ factory = self.factory adapter = self.adapter = factory.adapter self.model = TabularModel(editor=self) # Create the control control = self.control = self.widget_factory(self) # Set up the selection listener if factory.multi_select: self.sync_value(factory.selected, "multi_selected", "both", is_list=True) self.sync_value( factory.selected_row, "multi_selected_rows", "both", is_list=True, ) else: self.sync_value(factory.selected, "selected", "both") self.sync_value(factory.selected_row, "selected_row", "both") # Connect to the mode specific selection handler if factory.multi_select: slot = self._on_rows_selection else: slot = self._on_row_selection selection_model = self.control.selectionModel() selection_model.selectionChanged.connect(slot) # Synchronize other interesting traits as necessary: self.sync_value(factory.update, "update", "from", is_event=True) self.sync_value(factory.refresh, "refresh", "from", is_event=True) self.sync_value(factory.activated, "activated", "to") self.sync_value(factory.activated_row, "activated_row", "to") self.sync_value(factory.clicked, "clicked", "to") self.sync_value(factory.dclicked, "dclicked", "to") self.sync_value(factory.right_clicked, "right_clicked", "to") self.sync_value(factory.right_dclicked, "right_dclicked", "to") self.sync_value(factory.column_clicked, "column_clicked", "to") self.sync_value(factory.column_right_clicked, "column_right_clicked", "to") self.sync_value(factory.scroll_to_row, "scroll_to_row", "from", is_event=True) self.sync_value(factory.scroll_to_column, "scroll_to_column", "from", is_event=True) # Connect other signals as necessary control.activated.connect(self._on_activate) control.clicked.connect(self._on_click) control.clicked.connect(self._on_right_click) control.doubleClicked.connect(self._on_dclick) control.horizontalHeader().sectionClicked.connect( self._on_column_click) control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) control.customContextMenuRequested.connect(self._on_context_menu) self.header_event_filter = HeaderEventFilter(self) control.horizontalHeader().installEventFilter(self.header_event_filter) # Make sure we listen for 'items' changes as well as complete list # replacements: try: self.context_object.on_trait_change( self.update_editor, self.extended_name + "_items", dispatch="ui", ) except: pass # If the user has requested automatic update, attempt to set up the # appropriate listeners: if factory.auto_update: self.context_object.on_trait_change(self.refresh_editor, self.extended_name + ".-", dispatch="ui") # Create the mapping from user supplied images to QImages: for image_resource in factory.images: self._add_image(image_resource) # Refresh the editor whenever the adapter changes: self.on_trait_change(self.refresh_editor, "adapter.+update", dispatch="ui") # Rebuild the editor columns and headers whenever the adapter's # 'columns' changes: self.on_trait_change(self.update_editor, "adapter.columns", dispatch="ui") def dispose(self): """ Disposes of the contents of an editor. """ self.context_object.on_trait_change(self.update_editor, self.extended_name + "_items", remove=True) if self.factory.auto_update: self.context_object.on_trait_change(self.refresh_editor, self.extended_name + ".-", remove=True) self.on_trait_change(self.refresh_editor, "adapter.+update", remove=True) self.on_trait_change(self.update_editor, "adapter.columns", remove=True) self.adapter.cleanup() super(TabularEditor, self).dispose() def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ if not self._no_update: self.model.beginResetModel() self.model.endResetModel() if self.factory.multi_select: self._multi_selected_changed(self.multi_selected) else: self._selected_changed(self.selected) # ------------------------------------------------------------------------- # TabularEditor interface: # ------------------------------------------------------------------------- def refresh_editor(self): """ Requests the table view to redraw itself. """ self.control.viewport().update() def callx(self, func, *args, **kw): """ Call a function without allowing the editor to update. """ old = self._no_update self._no_update = True try: func(*args, **kw) finally: self._no_update = old def setx(self, **keywords): """ Set one or more attributes without allowing the editor to update. """ old = self._no_notify self._no_notify = True try: for name, value in keywords.items(): setattr(self, name, value) finally: self._no_notify = old # ------------------------------------------------------------------------- # UI preference save/restore interface: # ------------------------------------------------------------------------- def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ cws = prefs.get("cached_widths") num_columns = len(self.adapter.columns) if cws is not None and num_columns == len(cws): for column in range(num_columns): self.control.setColumnWidth(column, cws[column]) def save_prefs(self): """ Returns any user preference information associated with the editor. """ widths = [ self.control.columnWidth(column) for column in range(len(self.adapter.columns)) ] return {"cached_widths": widths} # ------------------------------------------------------------------------- # Private methods: # ------------------------------------------------------------------------- def _add_image(self, image_resource): """ Adds a new image to the image map. """ image = image_resource.create_icon() self.image_resources[image_resource] = image self.images[image_resource.name] = image return image def _get_image(self, image): """ Converts a user specified image to a QIcon. """ if isinstance(image, six.string_types): self.image = image image = self.image if isinstance(image, ImageResource): result = self.image_resources.get(image) if result is not None: return result return self._add_image(image) return self.images.get(image) def _mouse_click(self, index, trait): """ Generate a TabularEditorEvent event for a specified model index and editor trait name. """ event = TabularEditorEvent(editor=self, row=index.row(), column=index.column()) setattr(self, trait, event) # -- Trait Event Handlers ------------------------------------------------- def _clicked_changed(self): """ When mouse is clicked on a specific cell, update the selected indices first """ if not self.factory.multi_select: self.selected_row = self.clicked.row self.selected_column = self.clicked.column def _column_clicked_changed(self): """ When column is clicked, update selected column first """ if not self.factory.multi_select: self.selected_column = self.clicked.column def _update_changed(self): self.update_editor() def _refresh_changed(self): self.refresh_editor() def _selected_changed(self, new): if not self._no_update: if new is None: self._selected_row_changed(-1) else: try: selected_row = self.value.index(new) except Exception: from traitsui.api import raise_to_debug raise_to_debug() else: self._selected_row_changed(selected_row) def _selected_row_changed(self, selected_row): if not self._no_update: smodel = self.control.selectionModel() if selected_row < 0: smodel.clearSelection() else: smodel.select( self.model.index(selected_row, max(self.selected_column, 0)), QtGui.QItemSelectionModel.ClearAndSelect | QtGui.QItemSelectionModel.Rows, ) # Once selected, scroll to the row self.scroll_to_row = selected_row def _multi_selected_changed(self, new): if not self._no_update: values = self.value try: rows = [values.index(i) for i in new] except: pass else: self._multi_selected_rows_changed(rows) def _multi_selected_items_changed(self, event): values = self.value try: added = [values.index(item) for item in event.added] removed = [values.index(item) for item in event.removed] except: pass else: list_event = TraitListEvent(0, added, removed) self._multi_selected_rows_items_changed(list_event) def _multi_selected_rows_changed(self, selected_rows): if not self._no_update: smodel = self.control.selectionModel() selection = QtGui.QItemSelection() for row in selected_rows: selection.select(self.model.index(row, 0), self.model.index(row, 0)) smodel.clearSelection() smodel.select( selection, QtGui.QItemSelectionModel.Select | QtGui.QItemSelectionModel.Rows, ) def _multi_selected_rows_items_changed(self, event): if not self._no_update: smodel = self.control.selectionModel() for row in event.removed: smodel.select( self.model.index(row, 0), QtGui.QItemSelectionModel.Deselect | QtGui.QItemSelectionModel.Rows, ) for row in event.added: smodel.select( self.model.index(row, 0), QtGui.QItemSelectionModel.Select | QtGui.QItemSelectionModel.Rows, ) def _selected_column_changed(self, selected_column): if not self._no_update: smodel = self.control.selectionModel() if selected_column >= 0: smodel.select( self.model.index(max(self.selected_row, 0), selected_column), QtGui.QItemSelectionModel.ClearAndSelect | QtGui.QItemSelectionModel.Rows, ) # Once selected, scroll to the column self.scroll_to_column = selected_column scroll_to_row_hint_map = { "center": QtGui.QTableView.PositionAtCenter, "top": QtGui.QTableView.PositionAtTop, "bottom": QtGui.QTableView.PositionAtBottom, "visible": QtGui.QTableView.EnsureVisible, } def _scroll_to_row_changed(self, row): """ Scroll to the given row. """ scroll_hint = self.scroll_to_row_hint_map.get( self.factory.scroll_to_row_hint, self.control.PositionAtCenter) self.control.scrollTo( self.model.index(row, max(self.selected_column, 0)), scroll_hint) def _scroll_to_column_changed(self, column): """ Scroll to the given column. """ scroll_hint = self.scroll_to_row_hint_map.get( self.factory.scroll_to_row_hint, self.control.PositionAtCenter) self.control.scrollTo( self.model.index(max(self.selected_row, 0), column), scroll_hint) # -- Table Control Event Handlers ----------------------------------------- def _on_activate(self, index): """ Handle a cell being activated. """ self.activated_row = row = index.row() self.activated = self.adapter.get_item(self.object, self.name, row) def _on_click(self, index): """ Handle a cell being clicked. """ self._mouse_click(index, "clicked") def _on_dclick(self, index): """ Handle a cell being double clicked. """ self._mouse_click(index, "dclicked") def _on_column_click(self, column): event = TabularEditorEvent(editor=self, row=0, column=column) setattr(self, "column_clicked", event) def _on_right_click(self, column): event = TabularEditorEvent(editor=self, row=0, column=column) setattr(self, "right_clicked", event) def _on_column_right_click(self, column): event = TabularEditorEvent(editor=self, row=0, column=column) setattr(self, "column_right_clicked", event) def _on_row_selection(self, added, removed): """ Handle the row selection being changed. """ self._no_update = True try: indexes = self.control.selectionModel().selectedRows() if len(indexes): self.selected_row = indexes[0].row() self.selected = self.adapter.get_item(self.object, self.name, self.selected_row) else: self.selected_row = -1 self.selected = None finally: self._no_update = False def _on_rows_selection(self, added, removed): """ Handle the rows selection being changed. """ self._no_update = True try: indexes = self.control.selectionModel().selectedRows() selected_rows = [] selected = [] for index in indexes: row = index.row() selected_rows.append(row) selected.append( self.adapter.get_item(self.object, self.name, row)) self.multi_selected_rows = selected_rows self.multi_selected = selected finally: self._no_update = False def _on_context_menu(self, pos): column, row = ( self.control.columnAt(pos.x()), self.control.rowAt(pos.y()), ) menu = self.adapter.get_menu(self.object, self.name, row, column) if menu: self._menu_context = { "selection": self.object, "object": self.object, "editor": self, "column": column, "row": row, "item": self.adapter.get_item(self.object, self.name, row), "info": self.ui.info, "handler": self.ui.handler, } qmenu = menu.create_menu(self.control, self) qmenu.exec_(self.control.mapToGlobal(pos)) self._menu_context = None def _on_column_context_menu(self, pos): column = self.control.columnAt(pos.x()) menu = self.adapter.get_column_menu(self.object, self.name, -1, column) if menu: self._menu_context = { "selection": self.object, "object": self.object, "editor": self, "column": column, "info": self.ui.info, "handler": self.ui.handler, } qmenu = menu.create_menu(self.control, self) qmenu.exec_(self.control.mapToGlobal(pos)) self._menu_context = None else: # If no menu is defined on the adapter, just trigger a click event. self._on_column_right_click(column)
class SourceEditor(Editor): """ Editor for source code which uses the advanced code widget. """ # ------------------------------------------------------------------------- # Pyface PythonEditor interface: # ------------------------------------------------------------------------- #: Event that is fired on keypresses: key_pressed = Event(KeyPressedEvent) # ------------------------------------------------------------------------- # Editor interface: # ------------------------------------------------------------------------- #: The code editor is scrollable. This value overrides the default. scrollable = True # ------------------------------------------------------------------------- # SoureEditor interface: # ------------------------------------------------------------------------- #: Is the editor read only? readonly = Bool(False) #: The currently selected line selected_line = Int() #: The start position of the selected selected_start_pos = Int() #: The end position of the selected selected_end_pos = Int() #: The currently selected text selected_text = Str() #: The list of line numbers to mark mark_lines = List(Int) #: The current line number line = Event() #: The current column column = Event() #: The lines to be dimmed dim_lines = List(Int) dim_color = Str() dim_style_number = Int(16) # 0-15 are reserved for the python lexer #: The lines to have squiggles drawn under them squiggle_lines = List(Int) squiggle_color = Str() #: The lexer to use. lexer = Str() # ------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: # ------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ self.control = QtGui.QWidget() layout = QtGui.QVBoxLayout(self.control) layout.setContentsMargins(0, 0, 0, 0) self._widget = control = AdvancedCodeWidget(None, lexer=self.factory.lexer) layout.addWidget(control) factory = self.factory # Set up listeners for the signals we care about code_editor = self._widget.code if self.readonly: code_editor.setReadOnly(True) else: if factory.auto_set: code_editor.textChanged.connect(self.update_object) else: code_editor.focus_lost.connect(self.update_object) if factory.selected_text != "": code_editor.selectionChanged.connect(self._selection_changed) if (factory.line != "") or (factory.column != ""): code_editor.cursorPositionChanged.connect(self._position_changed) code_editor.line_number_widget.setVisible(factory.show_line_numbers) # Make sure the editor has been initialized: self.update_editor() # Set up any event listeners: self.sync_value(factory.mark_lines, "mark_lines", "from", is_list=True) self.sync_value(factory.selected_line, "selected_line", "from") self.sync_value(factory.selected_text, "selected_text", "to") self.sync_value(factory.line, "line") self.sync_value(factory.column, "column") self.sync_value(factory.selected_start_pos, "selected_start_pos", "to") self.sync_value(factory.selected_end_pos, "selected_end_pos", "to") self.sync_value(factory.dim_lines, "dim_lines", "from", is_list=True) if self.factory.dim_color == "": self.dim_color = "grey" else: self.sync_value(factory.dim_color, "dim_color", "from") self.sync_value(factory.squiggle_lines, "squiggle_lines", "from", is_list=True) if factory.squiggle_color == "": self.squiggle_color = "red" else: self.sync_value(factory.squiggle_color, "squiggle_color", "from") # Set the control tooltip: self.set_tooltip() def dispose(self): """ Disposes of the contents of an editor. """ # Make sure that the editor does not try to update as the control is # being destroyed: if not self.factory.auto_set: self._widget.code.focus_lost.disconnect(self.update_object) super(SourceEditor, self).dispose() def update_object(self): """ Handles the user entering input data in the edit control. """ if not self._locked: try: value = str(self._widget.code.toPlainText()) if isinstance(self.value, SequenceTypes): value = value.split() self.value = value except TraitError as excp: pass def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ self._locked = True new_value = self.value if isinstance(new_value, SequenceTypes): new_value = "\n".join([line.rstrip() for line in new_value]) control = self._widget if control.code.toPlainText() != new_value: control.code.setPlainText(new_value) if self.factory.selected_line: # TODO: update the factory selected line pass # TODO: put the cursor somewhere self._locked = False def error(self, excp): """ Handles an error that occurs while setting the object's trait value. """ pass # -- UI preference save/restore interface --------------------------------- def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ if self.factory.key_bindings is not None: key_bindings = prefs.get("key_bindings") if key_bindings is not None: self.factory.key_bindings.merge(key_bindings) def save_prefs(self): """ Returns any user preference information associated with the editor. """ return {"key_bindings": self.factory.key_bindings} def _mark_lines_changed(self): """ Handles the set of marked lines being changed. """ # FIXME: Not implemented at this time. return def _mark_lines_items_changed(self): self._mark_lines_changed() def _selection_changed(self): self.selected_text = str(self._widget.code.textCursor().selectedText()) start = self._widget.code.textCursor().selectionStart() end = self._widget.code.textCursor().selectionEnd() if start > end: start, end = end, start self.selected_start_pos = start self.selected_end_pos = end def _selected_line_changed(self): """ Handles a change in which line is currently selected. """ control = self._widget line = max(1, min(control.lines(), self.selected_line)) _, column = control.get_line_column() control.set_line_column(line, column) if self.factory.auto_scroll: control.centerCursor() def _line_changed(self, line): if not self._locked: _, column = self._widget.get_line_column() self._widget.set_line_column(line, column) if self.factory.auto_scroll: self._widget.centerCursor() def _column_changed(self, column): if not self._locked: line, _ = self._widget.get_line_column() self._widget.set_line_column(line, column) def _position_changed(self): """ Handles the cursor position being changed. """ control = self._widget self._locked = True self.line, self.column = control.get_line_column() self._locked = False self.selected_text = control.get_selected_text() if self.factory.auto_scroll: self._widget.centerCursor() def _key_pressed_changed(self, event): """ Handles a key being pressed within the editor. """ key_bindings = self.factory.key_bindings if key_bindings: processed = key_bindings.do(event.event, self.ui.handler, self.ui.info) else: processed = False if not processed and event.event.matches(QtGui.QKeySequence.Find): self._find_widget.show() def _dim_color_changed(self): pass def _squiggle_color_changed(self): pass @observe("dim_lines, squiggle_lines") def _style_document(self, event): self._widget.set_warn_lines(self.squiggle_lines)
class KeyBindings(HasPrivateTraits): """ A set of key bindings. """ #------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------- # Set of defined key bindings (redefined dynamically) bindings = List(KeyBinding) # Optional prefix to add to each method name prefix = Str # Optional suffix to add to each method name suffix = Str #-- Private Traits ------------------------------------------------------- # The (optional) list of controllers associated with this KeyBindings # object. The controllers may also be provided with the 'do' method: controllers = List(transient=True) # The 'parent' KeyBindings object of this one (if any): parent = Instance('KeyBindings', transient=True) # The root of the KeyBindings tree this object is part of: root = Property(depends_on='parent') # The child KeyBindings of this object (if any): children = List(transient=True) # Event fired when one of the contained KeyBinding objects is changed binding_modified = Event(KeyBinding) # Control that currently has the focus (if any) focus_owner = Any(transient=True) #------------------------------------------------------------------------- # Traits view definitions: #------------------------------------------------------------------------- traits_view = View([Item('bindings', style='custom', show_label=False, editor=ListEditor(style='custom')), '|{Click on an entry field, then press the key to ' 'assign. Double-click a field to clear it.}<>'], title='Update Key Bindings', kind='livemodal', resizable=True, width=0.4, height=0.4) #------------------------------------------------------------------------- # Initializes the object: #------------------------------------------------------------------------- def __init__(self, *bindings, **traits): super(KeyBindings, self).__init__(**traits) if (len(bindings) == 1) and isinstance(bindings[0], SequenceTypes): bindings = bindings[0] n = len(bindings) self.add_trait('bindings', List(KeyBinding, minlen=n, maxlen=n, mode='list')) self.bindings = [binding.trait_set(owner=self) for binding in bindings] #------------------------------------------------------------------------- # Processes a keyboard event: #------------------------------------------------------------------------- def do(self, event, controllers=[], *args, **kw): """ Processes a keyboard event. """ if isinstance(controllers, dict): controllers = list(controllers.values()) elif not isinstance(controllers, SequenceTypes): controllers = [controllers] else: controllers = list(controllers) return self._do(toolkit().key_event_to_name(event), controllers, args, kw.get('recursive', False)) #------------------------------------------------------------------------- # Merges another set of key bindings into this set: #------------------------------------------------------------------------- def merge(self, key_bindings): """ Merges another set of key bindings into this set. """ binding_dic = {} for binding in self.bindings: binding_dic[binding.method_name] = binding for binding in key_bindings.bindings: binding2 = binding_dic.get(binding.method_name) if binding2 is not None: binding2.binding1 = binding.binding1 binding2.binding2 = binding.binding2 #------------------------------------------------------------------------- # Returns a clone of the KeyBindings object: #------------------------------------------------------------------------- def clone(self, **traits): """ Returns a clone of the KeyBindings object. """ return self.__class__(*self.bindings, **traits).trait_set( **self.get('prefix', 'suffix')) #------------------------------------------------------------------------- # Dispose of the object: #------------------------------------------------------------------------- def dispose(self): """ Dispose of the object. """ if self.parent is not None: self.parent.children.remove(self) del self.controllers del self.children del self.bindings self.parent = self._root = self.focus_owner = None #------------------------------------------------------------------------- # Edits a possibly hierarchical set of KeyBindings: #------------------------------------------------------------------------- def edit(self): """ Edits a possibly hierarchical set of KeyBindings. """ bindings = list(set(self.root._get_bindings([]))) bindings.sort(key=lambda x: '%s%02d' % (x.binding1[-1:], x.binding1)) KeyBindings(bindings).edit_traits() #------------------------------------------------------------------------- # Returns the current binding for a specified key (if any): #------------------------------------------------------------------------- def key_binding_for(self, binding, key_name): """ Returns the current binding for a specified key (if any). """ if key_name != '': for a_binding in self.bindings: if ((a_binding is not binding) and ((key_name == a_binding.binding1) or (key_name == a_binding.binding2))): return a_binding return None #-- Property Implementations --------------------------------------------- @cached_property def _get_root(self): root = self while root.parent is not None: root = root.parent return root #-- Event Handlers ------------------------------------------------------- def _binding_modified_changed(self, binding): """ Handles a binding being changed. """ binding1 = binding.binding1 binding2 = binding.binding2 for a_binding in self.bindings: if binding is not a_binding: if binding1 == a_binding.binding1: a_binding.binding1 = '' if binding1 == a_binding.binding2: a_binding.binding2 = '' if binding2 == a_binding.binding1: a_binding.binding1 = '' if binding2 == a_binding.binding2: a_binding.binding2 = '' def _focus_owner_changed(self, old, new): """ Handles the focus owner being changed. """ if old is not None: old.border_size = 0 @on_trait_change('children[]') def _children_modified(self, removed, added): """ Handles child KeyBindings being added to the object. """ for item in added: item.parent = self #-- object Method Overrides ---------------------------------------------- #------------------------------------------------------------------------- # Restores the state of a previously pickled object: #------------------------------------------------------------------------- def __setstate__(self, state): """ Restores the state of a previously pickled object. """ n = len(state['bindings']) self.add_trait('bindings', List(KeyBinding, minlen=n, maxlen=n)) self.__dict__.update(state) self.bindings = self.bindings[:] #-- Private Methods ------------------------------------------------------ def _get_bindings(self, bindings): """ Returns all of the bindings of this object and all of its children. """ bindings.extend(self.bindings) for child in self.children: child._get_bindings(bindings) return bindings def _do(self, key_name, controllers, args, recursive): """ Process the specified key for the specified set of controllers for this KeyBindings object and all of its children. """ # Search through our own bindings for a match: for binding in self.bindings: if (key_name == binding.binding1) or ( key_name == binding.binding2): method_name = '%s%s%s' % ( self.prefix, binding.method_name, self.suffix) for controller in (controllers + self.controllers): method = getattr(controller, method_name, None) if method is not None: result = method(*args) if result is not False: return True if binding.method_name == 'edit_bindings': self.edit() return True # If recursive, continue searching through a children's bindings: if recursive: for child in self.children: if child._do(key_name, controllers, args, recursive): return True # Indicate no one processed the key: return False
class SceneModel(TVTKScene): ######################################## # TVTKScene traits. picker = Property ######################################## # SceneModel traits. # A convenient dictionary based interface to add/remove actors and widgets. # This is similar to the interface provided for the ActorEditor. actor_map = Dict() # This is used primarily to implement the add_actor/remove_actor methods. actor_list = List() # The actual scene being edited. scene_editor = Instance(TVTKScene) do_render = Event() # Fired when this is activated. activated = Event() # The control is going to be closed. closing = Event() # This exists just to mirror the TVTKWindow api. scene = Property ################################### # View related traits. # Render_window's view. _stereo_view = Group(Item(name='stereo_render'), Item(name='stereo_type'), show_border=True, label='Stereo rendering', ) # The default view of this object. default_view = View(Group( Group(Item(name='background'), Item(name='foreground'), Item(name='parallel_projection'), Item(name='disable_render'), Item(name='off_screen_rendering'), Item(name='jpeg_quality'), Item(name='jpeg_progressive'), Item(name='magnification'), Item(name='anti_aliasing_frames'), ), Group(Item(name='render_window', style='custom', visible_when='object.stereo', editor=InstanceEditor(view=View(_stereo_view)), show_label=False), ), label='Scene'), Group(Item(name='light_manager', style='custom', editor=InstanceEditor(), show_label=False), label='Lights') ) ################################### # Private traits. # Used by the editor to determine if the widget was enabled or not. enabled_info = Dict() def __init__(self, parent=None, **traits): """ Initializes the object. """ # Base class constructor. We call TVTKScene's super here on purpose. # Calling TVTKScene's init will create a new window which we do not # want. super(TVTKScene, self).__init__(**traits) self.control = None ###################################################################### # TVTKScene API. ###################################################################### def render(self): """ Force the scene to be rendered. Nothing is done if the `disable_render` trait is set to True.""" self.do_render = True def add_actors(self, actors): """ Adds a single actor or a tuple or list of actors to the renderer.""" if hasattr(actors, '__iter__'): self.actor_list.extend(actors) else: self.actor_list.append(actors) def remove_actors(self, actors): """ Removes a single actor or a tuple or list of actors from the renderer.""" my_actors = self.actor_list if hasattr(actors, '__iter__'): for actor in actors: my_actors.remove(actor) else: my_actors.remove(actors) # Conevenience methods. add_actor = add_actors remove_actor = remove_actors def add_widgets(self, widgets, enabled=True): """Adds widgets to the renderer. """ if not hasattr(widgets, '__iter__'): widgets = [widgets] for widget in widgets: self.enabled_info[widget] = enabled self.add_actors(widgets) def remove_widgets(self, widgets): """Removes widgets from the renderer.""" if not hasattr(widgets, '__iter__'): widgets = [widgets] self.remove_actors(widgets) for widget in widgets: del self.enabled_info[widget] def reset_zoom(self): """Reset the camera so everything in the scene fits.""" if self.scene_editor is not None: self.scene_editor.reset_zoom() def save(self, file_name, size=None, **kw_args): """Saves rendered scene to one of several image formats depending on the specified extension of the filename. If an additional size (2-tuple) argument is passed the window is resized to the specified size in order to produce a suitably sized output image. Please note that when the window is resized, the window may be obscured by other widgets and the camera zoom is not reset which is likely to produce an image that does not reflect what is seen on screen. Any extra keyword arguments are passed along to the respective image format's save method. """ self._check_scene_editor() self.scene_editor.save(file_name, size, **kw_args) def save_ps(self, file_name): """Saves the rendered scene to a rasterized PostScript image. For vector graphics use the save_gl2ps method.""" self._check_scene_editor() self.scene_editor.save_ps(file_name) def save_bmp(self, file_name): """Save to a BMP image file.""" self._check_scene_editor() self.scene_editor.save_bmp(file_name) def save_tiff(self, file_name): """Save to a TIFF image file.""" self._check_scene_editor() self.scene_editor.save_tiff(file_name) def save_png(self, file_name): """Save to a PNG image file.""" self._check_scene_editor() self.scene_editor.save_png(file_name) def save_jpg(self, file_name, quality=None, progressive=None): """Arguments: file_name if passed will be used, quality is the quality of the JPEG(10-100) are valid, the progressive arguments toggles progressive jpegs.""" self._check_scene_editor() self.scene_editor.save_jpg(file_name, quality, progressive) def save_iv(self, file_name): """Save to an OpenInventor file.""" self._check_scene_editor() self.scene_editor.save_iv(file_name) def save_vrml(self, file_name): """Save to a VRML file.""" self._check_scene_editor() self.scene_editor.save_vrml(file_name) def save_oogl(self, file_name): """Saves the scene to a Geomview OOGL file. Requires VTK 4 to work.""" self._check_scene_editor() self.scene_editor.save_oogl(file_name) def save_rib(self, file_name, bg=0, resolution=None, resfactor=1.0): """Save scene to a RenderMan RIB file. Keyword Arguments: file_name -- File name to save to. bg -- Optional background option. If 0 then no background is saved. If non-None then a background is saved. If left alone (defaults to None) it will result in a pop-up window asking for yes/no. resolution -- Specify the resolution of the generated image in the form of a tuple (nx, ny). resfactor -- The resolution factor which scales the resolution. """ self._check_scene_editor() self.scene_editor.save_rib(file_name, bg, resolution, resfactor) def save_wavefront(self, file_name): """Save scene to a Wavefront OBJ file. Two files are generated. One with a .obj extension and another with a .mtl extension which contains the material proerties. Keyword Arguments: file_name -- File name to save to """ self._check_scene_editor() self.scene_editor.save_wavefront(file_name) def save_gl2ps(self, file_name, exp=None): """Save scene to a vector PostScript/EPS/PDF/TeX file using GL2PS. If you choose to use a TeX file then note that only the text output is saved to the file. You will need to save the graphics separately. Keyword Arguments: file_name -- File name to save to. exp -- Optionally configured vtkGL2PSExporter object. Defaults to None and this will use the default settings with the output file type chosen based on the extention of the file name. """ self._check_scene_editor() self.scene_editor.save_gl2ps(file_name, exp) def save_povray(self, file_name): self._check_scene_editor() self.scene_editor.save_povray(file_name) def save_x3d(self, filename): self._check_scene_editor() self.scene_editor.save_x3d(file_name) def get_size(self): """Return size of the render window.""" self._check_scene_editor() return self.scene_editor.get_size() def set_size(self, size): """Set the size of the window.""" self._check_scene_editor() self.scene_editor.set_size(size) def _update_view(self, x, y, z, vx, vy, vz): """Used internally to set the view.""" if self.scene_editor is not None: self.scene_editor._update_view(x, y, z, vx, vy, vz) def _check_scene_editor(self): if self.scene_editor is None: msg = """ This method requires that there be an active scene editor. To do this, you will typically need to invoke:: object.edit_traits() where object is the object that contains the SceneModel. """ raise SceneModelError(msg) def _scene_editor_changed(self, old, new): if new is None: self._renderer = None self._renwin = None self._interactor = None else: self._renderer = new._renderer self._renwin = new._renwin self._interactor = new._interactor def _get_picker(self): """Getter for the picker.""" se = self.scene_editor if se is not None and hasattr(se, 'picker'): return se.picker return None def _get_light_manager(self): """Getter for the light manager.""" se = self.scene_editor if se is not None: return se.light_manager return None ###################################################################### # SceneModel API. ###################################################################### def _get_scene(self): """Getter for the scene property.""" return self
class MEditor(MWorkbenchPart): """ Mixin containing common code for toolkit-specific implementations. """ # 'IEditor' interface -------------------------------------------------# # The optional command stack. command_stack = Instance("pyface.undo.api.ICommandStack") # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? dirty = Bool(False) # The object that the editor is editing. # # The framework sets this when the editor is created. obj = Any() # Editor Lifecycle Events ---------------------------------------------# # Fired when the editor is opening. opening = VetoableEvent() # Fired when the editor has been opened. open = Event() # Fired when the editor is closing. closing = Event(VetoableEvent) # Fired when the editor is closed. closed = Event() # ------------------------------------------------------------------------ # 'object' interface. # ------------------------------------------------------------------------ def __str__(self): """ Return an informal string representation of the object. """ return "Editor(%s)" % self.id # ------------------------------------------------------------------------ # 'IWorkbenchPart' interface. # ------------------------------------------------------------------------ def _id_default(self): """ Trait initializer. """ # If no Id is specified then use a random uuid # this gaurantees (barring *really* unusual cases) that there are no # collisions between the ids of editors. return uuid.uuid4().hex # ------------------------------------------------------------------------ # 'IEditor' interface. # ------------------------------------------------------------------------ def close(self): """ Close the editor. """ if self.control is not None: self.closing = event = Vetoable() if not event.veto: self.window.close_editor(self) self.closed = True return # Initializers --------------------------------------------------------- def _command_stack_default(self): """ Trait initializer. """ # We make sure the undo package is entirely optional. try: from pyface.undo.api import CommandStack except ImportError: return None return CommandStack(undo_manager=self.window.workbench.undo_manager)
class PropertyListDemo(HasPrivateTraits): """ Displays a random list of Person objects in a TableEditor that is refreshed every 3 seconds by a background thread. """ # An event used to trigger a Property value update: ticker = Event() # The property being display in the TableEditor: people = Property(List, depends_on='ticker') # Tiny hack to allow starting the background thread easily: begin = Int() # -- Traits View Definitions ---------------------------------------------- traits_view = View(Item('people', show_label=False, editor=TableEditor( columns=[ ObjectColumn(name='name', editable=False, width=0.50), ObjectColumn(name='age', editable=False, width=0.15), ObjectColumn(name='gender', editable=False, width=0.35) ], auto_size=False, show_toolbar=False, sortable=False, )), title='Property List Demo', width=0.25, height=0.33, resizable=True) # -- Property Implementations --------------------------------------------- @cached_property def _get_people(self): """ Returns the value for the 'people' property. """ return [ Person(name='%s %s' % (choice(['Tom', 'Dick', 'Harry', 'Alice', 'Lia', 'Vibha']), choice(['Thomas', 'Jones', 'Smith', 'Adams', 'Johnson'])), age=randint(21, 75), gender=choice(['Male', 'Female'])) for i in range(randint(10, 20)) ] # -- Default Value Implementations ---------------------------------------- def _begin_default(self): """ Starts the background thread running. """ thread = Thread(target=self._timer) thread.setDaemon(True) thread.start() return 0 # -- Private Methods ------------------------------------------------------ def _timer(self): """ Triggers a property update every 3 seconds for 30 seconds. """ for i in range(10): sleep(3) self.ticker = True
class View(ViewElement): """ A Traits-based user interface for one or more objects. The attributes of the View object determine the contents and layout of an attribute-editing window. A View object contains a set of Group, Item, and Include objects. A View object can be an attribute of an object derived from HasTraits, or it can be a standalone object. """ # ------------------------------------------------------------------------- # Trait definitions: # ------------------------------------------------------------------------- #: A unique identifier for the view: id = AnId #: The top-level Group object for the view: content = Content #: The menu bar for the view. Usually requires a custom **handler**: menubar = Any # Instance( pyface.action.MenuBarManager ) #: The toolbar for the view. Usually requires a custom **handler**: toolbar = Any # Instance( pyface.action.ToolBarManager ) #: Status bar items to add to the view's status bar. The value can be: #: #: - **None**: No status bar for the view (the default). #: - string: Same as [ StatusItem( name = string ) ]. #: - StatusItem: Same as [ StatusItem ]. #: - [ [StatusItem|string], ... ]: Create a status bar with one field for #: each StatusItem in the list (or tuple). The status bar fields are #: defined from left to right in the order specified. A string value is #: converted to: StatusItem( name = string ): statusbar = ViewStatus #: List of button actions to add to the view. The **traitsui.menu** #: module defines standard buttons, such as **OKButton**, and standard sets #: of buttons, such as **ModalButtons**, which can be used to define a value #: for this attribute. This value can also be a list of button name strings, #: such as ``['OK', 'Cancel', 'Help']``. If set to the empty list, the #: view contains a default set of buttons (equivalent to **LiveButtons**: #: Undo/Redo, Revert, OK, Cancel, Help). To suppress buttons in the view, #: use the **NoButtons** variable, defined in **traitsui.menu**. buttons = Buttons #: The default button to activate when Enter is pressed. If not specified, #: pressing Enter will not activate any button. default_button = AButton #: The set of global key bindings for the view. Each time a key is pressed #: while the view has keyboard focus, the key is checked to see if it is one #: of the keys recognized by the KeyBindings object. If it is, the matching #: KeyBinding's method name is checked to see if it is defined on any of the #: object's in the view's context. If it is, the method is invoked. If the #: result of the method is **False**, then the search continues with the #: next object in the context. If any invoked method returns a non-False #: value, processing stops and the key is marked as having been handled. If #: all invoked methods return **False**, or no matching KeyBinding object is #: found, the key is processed normally. If the view has a non-empty *id* #: trait, the contents of the **KeyBindings** object will be saved as part #: of the view's persistent data: key_bindings = AKeyBindings #: The Handler object that provides GUI logic for handling events in the #: window. Set this attribute only if you are using a custom Handler. If #: not set, the default Traits UI Handler is used. handler = AHandler #: The factory function for converting a model into a model/view object: model_view = AModelView #: Title for the view, displayed in the title bar when the view appears as a #: secondary window (i.e., dialog or wizard). If not specified, "Edit #: properties" is used as the title. title = ATitle #: The name of the icon to display in the dialog window title bar: icon = Image #: The kind of user interface to create: kind = AKind #: The default object being edited: object = AnObject #: The default editor style of elements in the view: style = EditorStyle #: The default docking style to use for sub-groups of the view. The following #: values are possible: #: #: * 'fixed': No rearrangement of sub-groups is allowed. #: * 'horizontal': Moveable elements have a visual "handle" to the left by #: which the element can be dragged. #: * 'vertical': Moveable elements have a visual "handle" above them by #: which the element can be dragged. #: * 'tabbed': Moveable elements appear as tabbed pages, which can be #: arranged within the window or "stacked" so that only one appears at #: at a time. dock = DockStyle #: The image to display on notebook tabs: image = Image #: Called when modal changes are applied or reverted: on_apply = OnApply #: Can the user resize the window? resizable = IsResizable #: Can the user scroll the view? If set to True, window-level scroll bars #: appear whenever the window is too small to show all of its contents at #: one time. If set to False, the window does not scroll, but individual #: widgets might still contain scroll bars. scrollable = IsScrollable #: The category of exported elements: export = ExportType #: The valid categories of imported elements: imports = ImportTypes #: External help context identifier, which can be used by a custom help #: handler. This attribute is ignored by the default help handler. help_id = HelpId #: Requested x-coordinate (horizontal position) for the view window. This #: attribute can be specified in the following ways: #: #: * A positive integer: indicates the number of pixels from the left edge #: of the screen to the left edge of the window. #: * A negative integer: indicates the number of pixels from the right edge #: of the screen to the right edge of the window. #: * A floating point value between 0 and 1: indicates the fraction of the #: total screen width between the left edge of the screen and the left edge #: of the window. #: * A floating point value between -1 and 0: indicates the fraction of the #: total screen width between the right edge of the screen and the right #: edge of the window. x = XCoordinate #: Requested y-coordinate (vertical position) for the view window. This #: attribute behaves exactly like the **x** attribute, except that its value #: indicates the position of the top or bottom of the view window relative #: to the top or bottom of the screen. y = YCoordinate #: Requested width for the view window, as an (integer) number of pixels, or #: as a (floating point) fraction of the screen width. width = Width #: Requested height for the view window, as an (integer) number of pixels, or #: as a (floating point) fraction of the screen height. height = Height #: Class of dropped objects that can be added: drop_class = Any() #: Event when the view has been updated: updated = Event() #: What result should be returned if the user clicks the window or dialog #: close button or icon? close_result = CloseResult #: Note: Group objects delegate their 'object' and 'style' traits to the #: View # -- Deprecated Traits (DO NOT USE) --------------------------------------- ok = Bool(False) cancel = Bool(False) undo = Bool(False) redo = Bool(False) apply = Bool(False) revert = Bool(False) help = Bool(False) def __init__(self, *values, **traits): """ Initializes the object. """ ViewElement.__init__(self, **traits) self.set_content(*values) def set_content(self, *values): """ Sets the content of a view. """ content = [] accum = [] for value in values: if isinstance(value, ViewSubElement): content.append(value) elif type(value) in SequenceTypes: content.append(Group(*value)) elif (isinstance(value, str) and (value[:1] == "<") and (value[-1:] == ">")): # Convert string to an Include value: content.append(Include(value[1:-1].strip())) else: content.append(Item(value)) # If there are any 'Item' objects in the content, wrap the content in a # Group: for item in content: if isinstance(item, Item): content = [Group(*content)] break # Wrap all of the content up into a Group and save it as our content: self.content = Group(container=self, *content) def ui( self, context, parent=None, kind=None, view_elements=None, handler=None, id="", scrollable=None, args=None, ): """ Creates a **UI** object, which generates the actual GUI window or panel from a set of view elements. Parameters ---------- context : object or dictionary A single object or a dictionary of string/object pairs, whose trait attributes are to be edited. If not specified, the current object is used. parent : window component The window parent of the View object's window kind : string The kind of window to create. See the **AKind** trait for details. If *kind* is unspecified or None, the **kind** attribute of the View object is used. view_elements : ViewElements object The set of Group, Item, and Include objects contained in the view. Do not use this parameter when calling this method directly. handler : Handler object A handler object used for event handling in the dialog box. If None, the default handler for Traits UI is used. id : string A unique ID for persisting preferences about this user interface, such as size and position. If not specified, no user preferences are saved. scrollable : Boolean Indicates whether the dialog box should be scrollable. When set to True, scroll bars appear on the dialog box if it is not large enough to display all of the items in the view at one time. """ handler = handler or self.handler or default_handler() if not isinstance(handler, Handler): handler = handler() if args is not None: handler.trait_set(**args) if not isinstance(context, dict): context = context.trait_context() context.setdefault("handler", handler) handler = context["handler"] if self.model_view is not None: context["object"] = self.model_view(context["object"]) self_id = self.id if self_id != "": if id != "": id = "%s:%s" % (self_id, id) else: id = self_id if scrollable is None: scrollable = self.scrollable ui = UI( view=self, context=context, handler=handler, view_elements=view_elements, title=self.title, id=id, scrollable=scrollable, ) if kind is None: kind = self.kind ui.ui(parent, kind) return ui def replace_include(self, view_elements): """ Replaces any items that have an ID with an Include object with the same ID, and puts the object with the ID into the specified ViewElements object. """ if self.content is not None: self.content.replace_include(view_elements) def __repr__(self): """ Returns a "pretty print" version of the View. """ if self.content is None: return "()" return "( %s )" % ", ".join( [item.__repr__() for item in self.content.content])
class DataFrameViewer(HasTraits): data = Instance(DataFrame) df_refreshed = Event() view = View( Item("data", editor=DataFrameEditor(refresh="df_refreshed")))
class UI(HasPrivateTraits): """ Information about the user interface for a View. """ # ------------------------------------------------------------------------- # Trait definitions: # ------------------------------------------------------------------------- #: The ViewElements object from which this UI resolves Include items view_elements = Instance(ViewElements) #: Context objects that the UI is editing context = Dict(Str, Any) #: Handler object used for event handling handler = Instance(Handler) #: View template used to construct the user interface view = Instance("traitsui.view.View") #: Panel or dialog associated with the user interface control = Any() #: The parent UI (if any) of this UI parent = Instance("UI") #: Toolkit-specific object that "owns" **control** owner = Any() #: UIInfo object containing context or editor objects info = Instance(UIInfo) #: Result from a modal or wizard dialog: result = Bool(False) #: Undo and Redo history history = Any() #: The KeyBindings object (if any) for this UI: key_bindings = Property(depends_on=["view._key_bindings", "context"]) #: The unique ID for this UI for persistence id = Str() #: Have any modifications been made to UI contents? modified = Bool(False) #: Event when the user interface has changed updated = Event(Bool) #: Title of the dialog, if any title = Str() #: The ImageResource of the icon, if any icon = Image #: Should the created UI have scroll bars? scrollable = Bool(False) #: The number of currently pending editor error conditions errors = Int() #: The code used to rebuild an updated user interface rebuild = Callable() #: Set to True when the UI has finished being destroyed. destroyed = Bool(False) # -- Private Traits ------------------------------------------------------- #: Original context when used with a modal dialog _context = Dict(Str, Any) #: Copy of original context used for reverting changes _revert = Dict(Str, Any) #: List of methods to call once the user interface is created _defined = List() #: List of (visible_when,Editor) pairs _visible = List() #: List of (enabled_when,Editor) pairs _enabled = List() #: List of (checked_when,Editor) pairs _checked = List() #: Search stack used while building a user interface _search = List() #: List of dispatchable Handler methods _dispatchers = List() #: List of editors used to build the user interface _editors = List() #: List of names bound to the **info** object _names = List() #: Index of currently the active group in the user interface _active_group = Int() #: List of top-level groups used to build the user interface _groups = Property() _groups_cache = Any() #: Count of levels of nesting for undoable actions _undoable = Int(-1) #: Code used to rebuild an updated user interface _rebuild = Callable() #: The statusbar listeners that have been set up: _statusbar = List() #: Control which gets focus after UI is created #: Note: this does not track focus after UI creation #: only used by Qt backend. _focus_control = Any() #: Does the UI contain any scrollable widgets? #: #: The _scrollable trait is set correctly, but not used currently because #: its value is arrived at too late to be of use in building the UI. _scrollable = Bool(False) #: Cache for key bindings. _key_bindings = Instance("traitsui.key_bindings.KeyBindings") #: List of traits that are reset when a user interface is recycled #: (i.e. rebuilt). recyclable_traits = [ "_context", "_revert", "_defined", "_visible", "_enabled", "_checked", "_search", "_dispatchers", "_editors", "_names", "_active_group", "_undoable", "_rebuild", "_groups_cache", "_key_bindings", "_focus_control", ] #: List of additional traits that are discarded when a user interface is #: disposed. disposable_traits = [ "view_elements", "info", "handler", "context", "view", "history", "key_bindings", "icon", "rebuild", ] def traits_init(self): """ Initializes the traits object. """ self.info = UIInfo(ui=self) self.handler.init_info(self.info) def ui(self, parent, kind): """ Creates a user interface from the associated View template object. """ if (parent is None) and (kind in kind_must_have_parent): kind = "live" self.view.on_trait_change(self._updated_changed, "updated", dispatch="ui") self.rebuild = getattr(toolkit(), "ui_" + kind) self.rebuild(self, parent) def dispose(self, result=None, abort=False): """ Disposes of the contents of a user interface. """ if result is not None: self.result = result # Only continue if the view has not already been disposed of: if self.control is not None: # Save the user preference information for the user interface: if not abort: self.save_prefs() # Finish disposing of the user interface: self.finish() def recycle(self): """ Recycles the user interface prior to rebuilding it. """ # Reset all user interface editors: self.reset(destroy=False) # Discard any context object associated with the ui view control: self.control._object = None # Reset all recyclable traits: self.reset_traits(self.recyclable_traits) def finish(self): """ Finishes disposing of a user interface. """ # Destroy the control early to silence cascade events when the UI # enters an inconsistent state. toolkit().destroy_control(self.control) # Reset the contents of the user interface self.reset(destroy=False) # Make sure that 'visible', 'enabled', and 'checked' handlers are not # called after the editor has been disposed: for object in self.context.values(): object.on_trait_change(self._evaluate_when, remove=True) # Notify the handler that the view has been closed: self.handler.closed(self.info, self.result) # Clear the back-link from the UIInfo object to us: self.info.ui = None # Destroy the view control: self.control._object = None self.control = None # Dispose of any KeyBindings object we reference: if self._key_bindings is not None: self._key_bindings.dispose() # Break the linkage to any objects in the context dictionary: self.context.clear() # Remove specified symbols from our dictionary to aid in clean-up: self.reset_traits(self.recyclable_traits) self.reset_traits(self.disposable_traits) self.destroyed = True def reset(self, destroy=True): """ Resets the contents of a user interface. """ for editor in self._editors: if editor._ui is not None: # Propagate result to enclosed ui objects: editor._ui.result = self.result editor.dispose() # Zap the control. If there are pending events for the control in # the UI queue, the editor's '_update_editor' method will see that # the control is None and discard the update request: editor.control = None # Remove any statusbar listeners that have been set up: for object, handler, name in self._statusbar: object.on_trait_change(handler, name, remove=True) del self._statusbar[:] if destroy: toolkit().destroy_children(self.control) for dispatcher in self._dispatchers: dispatcher.remove() def find(self, include): """ Finds the definition of the specified Include object in the current user interface building context. """ context = self.context result = None # Get the context 'object' (if available): if len(context) == 1: object = list(context.values())[0] else: object = context.get("object") # Try to use our ViewElements objects: ve = self.view_elements # If none specified, try to get it from the UI context: if (ve is None) and (object is not None): # Use the context object's ViewElements (if available): ve = object.trait_view_elements() # Ask the ViewElements to find the requested item for us: if ve is not None: result = ve.find(include.id, self._search) # If not found, then try to search the 'handler' and 'object' for a # method we can call that will define it: if result is None: handler = context.get("handler") if handler is not None: method = getattr(handler, include.id, None) if callable(method): result = method() if (result is None) and (object is not None): method = getattr(object, include.id, None) if callable(method): result = method() return result def push_level(self): """ Returns the current search stack level. """ return len(self._search) def pop_level(self, level): """ Restores a previously pushed search stack level. """ del self._search[:len(self._search) - level] def prepare_ui(self): """ Performs all processing that occurs after the user interface is created. """ # Invoke all of the editor 'name_defined' methods we've accumulated: info = self.info.trait_set(initialized=False) for method in self._defined: method(info) # Then reset the list, since we don't need it anymore: del self._defined[:] # Synchronize all context traits with associated editor traits: self.sync_view() # Hook all keyboard events: toolkit().hook_events(self, self.control, "keys", self.key_handler) # Hook all events if the handler is an extended 'ViewHandler': handler = self.handler if isinstance(handler, ViewHandler): toolkit().hook_events(self, self.control) # Invoke the handler's 'init' method, and abort if it indicates # failure: if handler.init(info) == False: raise TraitError("User interface creation aborted") # For each Handler method whose name is of the form # 'object_name_changed', where 'object' is the name of an object in the # UI's 'context', create a trait notification handler that will call # the method whenever 'object's 'name' trait changes. Also invoke the # method immediately so initial user interface state can be correctly # set: context = self.context for name in self._each_trait_method(handler): if name[-8:] == "_changed": prefix = name[:-8] col = prefix.find("_", 1) if col >= 0: object = context.get(prefix[:col]) if object is not None: method = getattr(handler, name) trait_name = prefix[col + 1:] self._dispatchers.append( Dispatcher(method, info, object, trait_name)) if object.base_trait(trait_name).type != "event": method(info) # If there are any Editor object's whose 'visible', 'enabled' or # 'checked' state is controlled by a 'visible_when', 'enabled_when' or # 'checked_when' expression, set up an 'anytrait' changed notification # handler on each object in the 'context' that will cause the # 'visible', 'enabled' or 'checked' state of each affected Editor to be # set. Also trigger the evaluation immediately, so the visible, # enabled or checked state of each Editor can be correctly initialized: if (len(self._visible) + len(self._enabled) + len(self._checked)) > 0: for object in context.values(): object.on_trait_change(self._evaluate_when, dispatch="ui") self._do_evaluate_when(at_init=True) # Indicate that the user interface has been initialized: info.initialized = True def sync_view(self): """ Synchronize context object traits with view editor traits. """ for name, object in self.context.items(): self._sync_view(name, object, "sync_to_view", "from") self._sync_view(name, object, "sync_from_view", "to") self._sync_view(name, object, "sync_with_view", "both") def _sync_view(self, name, object, metadata, direction): info = self.info for trait_name, trait in object.traits(**{metadata: is_str}).items(): for sync in getattr(trait, metadata).split(","): try: editor_id, editor_name = [ item.strip() for item in sync.split(".") ] except: raise TraitError( "The '%s' metadata for the '%s' trait in " "the '%s' context object should be of the form: " "'id1.trait1[,...,idn.traitn]." % (metadata, trait_name, name)) editor = getattr(info, editor_id, None) if editor is not None: editor.sync_value("%s.%s" % (name, trait_name), editor_name, direction) else: raise TraitError( "No editor with id = '%s' was found for " "the '%s' metadata for the '%s' trait in the '%s' " "context object." % (editor_id, metadata, trait_name, name)) def get_extended_value(self, name): """ Gets the current value of a specified extended trait name. """ names = name.split(".") if len(names) > 1: value = self.context[names[0]] del names[0] else: value = self.context["object"] for name in names: value = getattr(value, name) return value def restore_prefs(self): """ Retrieves and restores any saved user preference information associated with the UI. """ id = self.id if id != "": db = self.get_ui_db() if db is not None: try: ui_prefs = db.get(id) db.close() return self.set_prefs(ui_prefs) except: pass return None def set_prefs(self, prefs): """ Sets the values of user preferences for the UI. """ if isinstance(prefs, dict): info = self.info for name in self._names: editor = getattr(info, name, None) if isinstance(editor, Editor) and (editor.ui is self): editor_prefs = prefs.get(name) if editor_prefs is not None: editor.restore_prefs(editor_prefs) if self.key_bindings is not None: key_bindings = prefs.get("$") if key_bindings is not None: self.key_bindings.merge(key_bindings) return prefs.get("") return None def save_prefs(self, prefs=None): """ Saves any user preference information associated with the UI. """ if prefs is None: toolkit().save_window(self) return id = self.id if id != "": db = self.get_ui_db(mode="c") if db is not None: db[id] = self.get_prefs(prefs) db.close() def get_prefs(self, prefs=None): """ Gets the preferences to be saved for the user interface. """ ui_prefs = {} if prefs is not None: ui_prefs[""] = prefs if self.key_bindings is not None: ui_prefs["$"] = self.key_bindings info = self.info for name in self._names: editor = getattr(info, name, None) if isinstance(editor, Editor) and (editor.ui is self): prefs = editor.save_prefs() if prefs is not None: ui_prefs[name] = prefs return ui_prefs def get_ui_db(self, mode="r"): """ Returns a reference to the Traits UI preference database. """ try: return shelve.open( os.path.join(traits_home(), "traits_ui"), flag=mode, protocol=-1, ) except: return None def get_editors(self, name): """ Returns a list of editors for the given trait name. """ return [editor for editor in self._editors if editor.name == name] def get_error_controls(self): """ Returns the list of editor error controls contained by the user interface. """ controls = [] for editor in self._editors: control = editor.get_error_control() if isinstance(control, list): controls.extend(control) else: controls.append(control) return controls def add_defined(self, method): """ Adds a Handler method to the list of methods to be called once the user interface has been constructed. """ self._defined.append(method) def add_visible(self, visible_when, editor): """ Adds a conditionally enabled Editor object to the list of monitored 'visible_when' objects. """ try: self._visible.append((compile(visible_when, "<string>", "eval"), editor)) except: pass # fixme: Log an error here... def add_enabled(self, enabled_when, editor): """ Adds a conditionally enabled Editor object to the list of monitored 'enabled_when' objects. """ try: self._enabled.append((compile(enabled_when, "<string>", "eval"), editor)) except: pass # fixme: Log an error here... def add_checked(self, checked_when, editor): """ Adds a conditionally enabled (menu) Editor object to the list of monitored 'checked_when' objects. """ try: self._checked.append((compile(checked_when, "<string>", "eval"), editor)) except: pass # fixme: Log an error here... def do_undoable(self, action, *args, **kw): """ Performs an action that can be undone. """ undoable = self._undoable try: if (undoable == -1) and (self.history is not None): self._undoable = self.history.now action(*args, **kw) finally: if undoable == -1: self._undoable = -1 def route_event(self, event): """ Routes a "hooked" event to the correct handler method. """ toolkit().route_event(self, event) def key_handler(self, event, skip=True): """ Handles key events. """ key_bindings = self.key_bindings handled = (key_bindings is not None) and key_bindings.do( event, [], self.info, recursive=(self.parent is None)) if (not handled) and (self.parent is not None): handled = self.parent.key_handler(event, False) if (not handled) and skip: toolkit().skip_event(event) return handled def evaluate(self, function, *args, **kw_args): """ Evaluates a specified function in the UI's **context**. """ if function is None: return None if callable(function): return function(*args, **kw_args) context = self.context.copy() context["ui"] = self context["handler"] = self.handler return eval(function, globals(), context)(*args, **kw_args) def eval_when(self, when, result=True): """ Evaluates an expression in the UI's **context** and returns the result. """ context = self._get_context(self.context) try: result = eval(when, globals(), context) except: from traitsui.api import raise_to_debug raise_to_debug() del context["ui"] return result def _get_context(self, context): """ Gets the context to use for evaluating an expression. """ name = "object" n = len(context) if (n == 2) and ("handler" in context): for name, value in context.items(): if name != "handler": break elif n == 1: name = list(context.keys())[0] value = context.get(name) if value is not None: context2 = value.trait_get() context2.update(context) else: context2 = context.copy() context2["ui"] = self return context2 def _evaluate_when(self): """ Set the 'visible', 'enabled', and 'checked' states for all Editors controlled by a 'visible_when', 'enabled_when' or 'checked_when' expression. """ self._do_evaluate_when(at_init=False) def _do_evaluate_when(self, at_init=False): """ Set the 'visible', 'enabled', and 'checked' states for all Editors. This function does the job of _evaluate_when. We define it here to work around the traits dispatching mechanism that automatically determines the number of arguments of a notification method. :attr:`at_init` is set to true when this function is called the first time at initialization. In that case, we want to force the state of the items to be set (normally it is set only if it changes). """ self._evaluate_condition(self._visible, "visible", at_init) self._evaluate_condition(self._enabled, "enabled", at_init) self._evaluate_condition(self._checked, "checked", at_init) def _evaluate_condition(self, conditions, trait, at_init=False): """ Evaluates a list of (eval, editor) pairs and sets a specified trait on each editor to reflect the Boolean value of the expression. 1) All conditions are evaluated 2) The elements whose condition evaluates to False are updated 3) The elements whose condition evaluates to True are updated E.g., we first make invisible all elements for which 'visible_when' evaluates to False, and then we make visible the ones for which 'visible_when' is True. This avoids mutually exclusive elements to be visible at the same time, and thus making a dialog unnecessarily large. The state of an editor is updated only when it changes, unless at_init is set to True. Parameters ---------- conditions : list of (str, Editor) tuple A list of tuples, each formed by 1) a string that contains a condition that evaluates to either True or False, and 2) the editor whose state depends on the condition trait : str The trait that is set by the condition. Either 'visible, 'enabled', or 'checked'. at_init : bool If False, the state of an editor is set only when it changes (e.g., a visible element would not be updated to visible=True again). If True, the state is always updated (used at initialization). """ context = self._get_context(self.context) # list of elements that should be activated activate = [] # list of elements that should be de-activated deactivate = [] for when, editor in conditions: try: cond_value = eval(when, globals(), context) editor_state = getattr(editor, trait) # add to update lists only if at_init is True (called on # initialization), or if the editor state has to change if cond_value and (at_init or not editor_state): activate.append(editor) if not cond_value and (at_init or editor_state): deactivate.append(editor) except Exception: # catch errors in the validate_when expression from traitsui.api import raise_to_debug raise_to_debug() # update the state of the editors for editor in deactivate: setattr(editor, trait, False) for editor in activate: setattr(editor, trait, True) def _get__groups(self): """ Returns the top-level Groups for the view (after resolving Includes. (Implements the **_groups** property.) """ if self._groups_cache is None: shadow_group = self.view.content.get_shadow(self) self._groups_cache = shadow_group.get_content() for item in self._groups_cache: if isinstance(item, Item): self._groups_cache = [ ShadowGroup( shadow=Group(*self._groups_cache), content=self._groups_cache, groups=1, ) ] break return self._groups_cache # -- Property Implementations --------------------------------------------- def _get_key_bindings(self): if self._key_bindings is None: # create a new key_bindings instance lazily view, context = self.view, self.context if (view is None) or (context is None): return None # Get the KeyBindings object to use: values = list(context.values()) key_bindings = view.key_bindings if key_bindings is None: from .key_bindings import KeyBindings self._key_bindings = KeyBindings(controllers=values) else: self._key_bindings = key_bindings.clone(controllers=values) return self._key_bindings # -- Traits Event Handlers ------------------------------------------------ def _updated_changed(self): if self.rebuild is not None: toolkit().rebuild_ui(self) def _title_changed(self): if self.control is not None: toolkit().set_title(self) def _icon_changed(self): if self.control is not None: toolkit().set_icon(self) @on_trait_change("parent, view, context") def _pvc_changed(self): parent = self.parent if (parent is not None) and (self.key_bindings is not None): # If we don't have our own history, use our parent's: if self.history is None: self.history = parent.history # Link our KeyBindings object as a child of our parent's # KeyBindings object (if any): if parent.key_bindings is not None: parent.key_bindings.children.append(self.key_bindings)
class Window(MWindow, Widget): """ The toolkit specific implementation of a Window. See the IWindow interface for the API documentation. """ # 'IWindow' interface -------------------------------------------------# position = Property(Tuple) size = Property(Tuple) title = Str() # Events ----- activated = Event() closed = Event() closing = Event() deactivated = Event() key_pressed = Event(KeyPressedEvent) opened = Event() opening = Event() # Private interface ---------------------------------------------------- # Shadow trait for position. _position = Tuple((-1, -1)) # Shadow trait for size. _size = Tuple((-1, -1)) # ------------------------------------------------------------------------ # 'IWindow' interface. # ------------------------------------------------------------------------ def show(self, visible): pass # ------------------------------------------------------------------------ # Protected 'IWindow' interface. # ------------------------------------------------------------------------ def _add_event_listeners(self): pass # ------------------------------------------------------------------------ # Private interface. # ------------------------------------------------------------------------ def _get_position(self): """ Property getter for position. """ return self._position def _set_position(self, position): """ Property setter for position. """ old = self._position self._position = position self.trait_property_changed("position", old, position) def _get_size(self): """ Property getter for size. """ return self._size def _set_size(self, size): """ Property setter for size. """ old = self._size self._size = size self.trait_property_changed("size", old, size)
class MRISubjectSource(HasPrivateTraits): """Find subjects in SUBJECTS_DIR and select one. Parameters ---------- subjects_dir : directory SUBJECTS_DIR. subject : str Subject, corresponding to a folder in SUBJECTS_DIR. """ refresh = Event(desc="Refresh the subject list based on the directory " "structure of subjects_dir.") # settings subjects_dir = Directory(exists=True) subjects = Property(List(Str), depends_on=['subjects_dir', 'refresh']) subject = Enum(values='subjects') # info can_create_fsaverage = Property(Bool, depends_on=['subjects_dir', 'subjects']) subject_has_bem = Property(Bool, depends_on=['subjects_dir', 'subject'], desc="whether the subject has a file matching " "the bem file name pattern") bem_pattern = Property(depends_on='mri_dir') @cached_property def _get_can_create_fsaverage(self): if not op.exists(self.subjects_dir) or 'fsaverage' in self.subjects: return False return True @cached_property def _get_mri_dir(self): if not self.subject: return elif not self.subjects_dir: return else: return op.join(self.subjects_dir, self.subject) @cached_property def _get_subjects(self): sdir = self.subjects_dir is_dir = sdir and op.isdir(sdir) if is_dir: dir_content = os.listdir(sdir) subjects = [s for s in dir_content if _is_mri_subject(s, sdir)] if len(subjects) == 0: subjects.append('') else: subjects = [''] return sorted(subjects) @cached_property def _get_subject_has_bem(self): if not self.subject: return False return _mri_subject_has_bem(self.subject, self.subjects_dir) def create_fsaverage(self): # noqa: D102 if not self.subjects_dir: raise RuntimeError( "No subjects directory is selected. Please specify " "subjects_dir first.") fs_home = get_fs_home() if fs_home is None: raise RuntimeError( "FreeSurfer contains files that are needed for copying the " "fsaverage brain. Please install FreeSurfer and try again.") create_default_subject(fs_home=fs_home, update=True, subjects_dir=self.subjects_dir) self.refresh = True self.subject = 'fsaverage' @on_trait_change('subjects_dir') def _emit_subject(self): # This silliness is the only way I could figure out to get the # on_trait_change('subject_panel.subject') in CoregFrame to work! self.subject = self.subject
class MRIHeadWithFiducialsModel(HasPrivateTraits): """Represent an MRI head shape (high and low res) with fiducials. Attributes ---------- points : array (n_points, 3) MRI head surface points. tris : array (n_tris, 3) Triangles based on points. lpa : array (1, 3) Left peri-auricular point coordinates. nasion : array (1, 3) Nasion coordinates. rpa : array (1, 3) Right peri-auricular point coordinates. """ subject_source = Instance(MRISubjectSource, ()) bem_low_res = Instance(SurfaceSource, ()) bem_high_res = Instance(SurfaceSource, ()) fid = Instance(FiducialsSource, ()) fid_file = DelegatesTo('fid', 'file') fid_fname = DelegatesTo('fid', 'fname') fid_points = DelegatesTo('fid', 'points') subjects_dir = DelegatesTo('subject_source') subject = DelegatesTo('subject_source') subject_has_bem = DelegatesTo('subject_source') lpa = Array(float, (1, 3)) nasion = Array(float, (1, 3)) rpa = Array(float, (1, 3)) reset = Event(desc="Reset fiducials to the file.") # info can_save = Property(depends_on=['file', 'can_save_as']) can_save_as = Property(depends_on=['lpa', 'nasion', 'rpa']) can_reset = Property(depends_on=['file', 'fid.points', 'lpa', 'nasion', 'rpa']) fid_ok = Property(depends_on=['lpa', 'nasion', 'rpa'], desc="All points " "are set") default_fid_fname = Property(depends_on=['subjects_dir', 'subject'], desc="the default file name for the " "fiducials fif file") # switch for the GUI (has no effect in the model) lock_fiducials = Bool(False, desc="Used by GIU, has no effect in the " "model.") @on_trait_change('fid_points') def reset_fiducials(self): # noqa: D102 if self.fid_points is not None: self.lpa = self.fid_points[0:1] self.nasion = self.fid_points[1:2] self.rpa = self.fid_points[2:3] def save(self, fname=None): """Save the current fiducials to a file. Parameters ---------- fname : str Destination file path. If None, will use the current fid filename if available, or else use the default pattern. """ if fname is None: fname = self.fid_file if not fname: fname = self.default_fid_fname dig = [{'kind': FIFF.FIFFV_POINT_CARDINAL, 'ident': FIFF.FIFFV_POINT_LPA, 'r': np.array(self.lpa[0])}, {'kind': FIFF.FIFFV_POINT_CARDINAL, 'ident': FIFF.FIFFV_POINT_NASION, 'r': np.array(self.nasion[0])}, {'kind': FIFF.FIFFV_POINT_CARDINAL, 'ident': FIFF.FIFFV_POINT_RPA, 'r': np.array(self.rpa[0])}] write_fiducials(fname, dig, FIFF.FIFFV_COORD_MRI) self.fid_file = fname @cached_property def _get_can_reset(self): if not self.fid_file: return False elif np.any(self.lpa != self.fid.points[0:1]): return True elif np.any(self.nasion != self.fid.points[1:2]): return True elif np.any(self.rpa != self.fid.points[2:3]): return True return False @cached_property def _get_can_save_as(self): can = not (np.all(self.nasion == self.lpa) or np.all(self.nasion == self.rpa) or np.all(self.lpa == self.rpa)) return can @cached_property def _get_can_save(self): if not self.can_save_as: return False elif self.fid_file: return True elif self.subjects_dir and self.subject: return True else: return False @cached_property def _get_default_fid_fname(self): fname = fid_fname.format(subjects_dir=self.subjects_dir, subject=self.subject) return fname @cached_property def _get_fid_ok(self): return all(np.any(pt) for pt in (self.nasion, self.lpa, self.rpa)) def _reset_fired(self): self.reset_fiducials() # if subject changed because of a change of subjects_dir this was not # triggered @on_trait_change('subjects_dir,subject') def _subject_changed(self): subject = self.subject subjects_dir = self.subjects_dir if not subjects_dir or not subject: return # find high-res head model (if possible) high_res_path = _find_head_bem(subject, subjects_dir, high_res=True) low_res_path = _find_head_bem(subject, subjects_dir, high_res=False) if high_res_path is None and low_res_path is None: msg = 'No standard head model was found for subject %s' % subject error(None, msg, "No head surfaces found") raise RuntimeError(msg) if high_res_path is not None: self.bem_high_res.file = high_res_path else: self.bem_high_res.file = low_res_path if low_res_path is None: # This should be very rare! warn('No low-resolution head found, decimating high resolution ' 'mesh (%d vertices): %s' % (len(self.bem_high_res.surf.rr), high_res_path,)) # Create one from the high res one, which we know we have rr, tris = decimate_surface(self.bem_high_res.surf.rr, self.bem_high_res.surf.tris, n_triangles=5120) surf = complete_surface_info(dict(rr=rr, tris=tris), copy=False, verbose=False) # directly set the attributes of bem_low_res self.bem_low_res.surf = Surf(tris=surf['tris'], rr=surf['rr'], nn=surf['nn']) else: self.bem_low_res.file = low_res_path # Set MNI points try: fids = get_mni_fiducials(subject, subjects_dir) except Exception: # some problem, leave at origin self.fid.mni_points = None else: self.fid.mni_points = np.array([f['r'] for f in fids], float) # find fiducials file fid_files = _find_fiducials_files(subject, subjects_dir) if len(fid_files) == 0: self.fid.reset_traits(['file']) self.lock_fiducials = False else: self.fid_file = fid_files[0].format(subjects_dir=subjects_dir, subject=subject) self.lock_fiducials = True # does not seem to happen by itself ... so hard code it: self.reset_fiducials()
class ScriptManager(HasTraits): """ The ScriptManager class is the default implementation of IScriptManager. """ #### 'IScriptManager' interface ########################################### # This event is fired whenever a scriptable object is bound or unbound. It # is intended to be used by an interactive Python shell to give the # advanced user access to the scriptable objects. If an object is created # via a factory then the event is fired when the factory is called, and not # when the factory is bound. bind_event = Event(IBindEvent) # This is set if user actions are being recorded as a script. It is # maintained by the script manager. recording = Bool(False) # This is the text of the script currently being recorded (or the last # recorded script if none is currently being recorded). It is updated # automatically as the user performs actions. script = Property(Unicode) # This event is fired when the recorded script changes. The value of the # event will be the ScriptManager instance. script_updated = Event(IScriptManager) #### Private interface #################################################### # The list of calls to scriptable calls. _calls = List(Instance(_ScriptCall)) # The dictionary of bound names. The value is the next numerical suffix # to use when the binding policy is 'auto'. _names = Dict # The dictionary of _BoundObject instances keyed by the name the object is # bound to. _namespace = Dict # The next sequential result number. _next_result_nr = Int # The results returned by previous scriptable calls. The key is the id() # of the result object. The value is a two element tuple of the sequential # result number (easier for the user to use than the id()) and the result # object itself. _results = Dict # The dictionary of _ScriptObject instances keyed by the object's id(). _so_by_id = Dict # The dictionary of _ScriptObject instances keyed by the a weak reference # to the object. _so_by_ref = Dict # The date and time when the script was recorded. _when_started = Any ########################################################################### # 'IScriptManager' interface. ########################################################################### def bind(self, obj, name=None, bind_policy='unique', api=None, includes=None, excludes=None): """ Bind obj to name and make (by default) its public methods and traits (ie. those not beginning with an underscore) scriptable. The default value of name is the type of obj with the first character forced to lower case. name may be a dotted name (eg. 'abc.def.xyz'). bind_policy determines what happens if the name is already bound. If the policy is 'auto' then a numerical suffix will be added to the name, if necessary, to make it unique. If the policy is 'unique' then an exception is raised. If the policy is 'rebind' then the previous binding is discarded. The default is 'unique' If api is given then it is a class, or a list of classes, that define the attributes that will be made scriptable. Otherwise if includes is given it is a list of names of attributes that will be made scriptable. Otherwise all the public attributes of scripted_type will be made scriptable except those in the excludes list. """ # Register the object. self.new_object(obj, obj.__class__, name=name, bind_policy=bind_policy) # Make it scriptable. make_object_scriptable(obj, api=api, includes=includes, excludes=excludes) def bind_factory(self, factory, name, bind_policy='unique', api=None, includes=None, excludes=None): """ Bind factory to name. This does the same as the bind() method except that it uses a factory that will be called later on to create the object only if the object is needed. See the documentation for bind() for a description of the remaining arguments. """ name = self._unique_name(name, bind_policy) self._namespace[name] = _FactoryObject(name=name, factory=factory, api=api, includes=includes, excludes=excludes) def run(self, script): """ Run the given script, either a string or a file-like object. """ # Initialise the namespace with all explicitly bound objects. nspace = LazyNamespace() for name, bo in self._namespace.items(): if bo.explicitly_bound: add_to_namespace(bo.obj, name, nspace) exec(script, nspace) def run_file(self, file_name): """ Run the given script file. """ f = open(file_name) self.run(f) f.close() def start_recording(self): """ Start the recording of user actions. The 'script' trait is cleared and all subsequent actions are added to 'script'. The 'recording' trait is updated appropriately. """ self._calls = [] self._next_result_nr = 0 self._results = {} self.recording = True self.script_updated = self def stop_recording(self): """ Stop the recording of user actions. The 'recording' trait is updated appropriately. """ self.recording = False ########################################################################### # 'ScriptManager' interface. ########################################################################### def record_method(self, func, args, kwargs): """ Record the call of a method of a ScriptableObject instance and return the result. This is intended to be used only by the scriptable decorator. """ if self.recording: # Record the arguments before the function has a chance to modify # them. srec = self._new_method(func, args, kwargs) result = func(*args, **kwargs) self._add_method(srec, result) self.script_updated = self else: result = func(*args, **kwargs) return result def record_trait_get(self, obj, name, result): """ Record the get of a trait of a scriptable object. This is intended to be used only by the Scriptable trait getter. """ if self.recording: side_effects = self._add_trait_get(obj, name, result) # Don't needlessly fire the event if there are no side effects. if side_effects: self.script_updated = self def record_trait_set(self, obj, name, value): """ Record the set of a trait of a scriptable object. This is intended to be used only by the Scriptable trait getter. """ if self.recording: self._add_trait_set(obj, name, value) self.script_updated = self def new_object(self, obj, scripted_type, args=None, kwargs=None, name=None, bind_policy='auto'): """ Register a scriptable object and the arguments used to create it. If no arguments were provided then assume the object is being explicitly bound. """ # The name defaults to the type name. if not name: name = scripted_type.__name__ name = name[0].lower() + name[1:] name = self._unique_name(name, bind_policy) obj_id = id(obj) obj_ref = weakref.ref(obj, self._gc_script_obj) so = _ScriptObject(name=name, obj_id=obj_id, obj_ref=obj_ref, scripted_type=scripted_type) # If we are told how to create the object then it must be implicitly # bound. if args is not None: # Convert each argument to its string representation if possible. # Doing this now avoids problems with mutable arguments. so.args = [self._scriptable_object_as_string(a) for a in args] for n, value in kwargs.items(): so.kwargs[n] = self._scriptable_object_as_string(value) so.explicitly_bound = False # Remember the scriptable object via the different access methods. self._so_by_id[obj_id] = so self._so_by_ref[obj_ref] = so self._namespace[name] = so # Note that if anything listening to this event doesn't use weak # references then the object will be kept alive. self.bind_event = BindEvent(name=name, obj=obj) @staticmethod def args_as_string_list(args, kwargs, so_needed=None): """ Return a complete argument list from sets of positional and keyword arguments. Update the optional so_needed list for those arguments that refer to a scriptable object. """ if so_needed is None: so_needed = [] all_args = [] for arg in args: s = ScriptManager.arg_as_string(arg, so_needed) all_args.append(s) for name, value in kwargs.items(): s = ScriptManager.arg_as_string(value, so_needed) all_args.append('%s=%s' % (name, s)) return all_args @staticmethod def arg_as_string(arg, so_needed): """ Return the string representation of an argument. Update the so_needed list if the argument refers to a scriptable object. Any delayed conversion exception is handled here. """ if isinstance(arg, Exception): raise arg if isinstance(arg, _ScriptObject): # Check it hasn't been unbound. if not arg.name: raise NameError( "%s has been unbound but is needed by the script" % arg.obj_ref()) # Add it to the needed list if it isn't already there. if arg not in so_needed: so_needed.append(arg) arg = arg.name return arg ########################################################################### # Private interface. ########################################################################### def _new_method(self, func, args, kwargs): """ Return an object that encapsulates a call to a scriptable method. _add_method() must be called to add it to the current script. """ # Convert each argument to its string representation if possible. # Doing this now avoids problems with mutable arguments. nargs = [self._object_as_string(arg) for arg in args] if type(func) is types.FunctionType: so = None else: so = nargs[0] nargs = nargs[1:] nkwargs = {} for name, value in kwargs.items(): nkwargs[name] = self._object_as_string(value) return _ScriptMethod(name=func.__name__, so=so, args=nargs, kwargs=nkwargs) def _add_method(self, entry, result): """ Add a method call (returned by _new_method()), with it's associated result and ID, to the current script. """ self._start_script() if result is not None: # Assume that a tuple represents multiple returned values - not # necessarily a valid assumption unless we make it a rule for # scriptable functions. if type(result) is type(()): for r in result: self._save_result(r) else: self._save_result(result) entry.result = result self._calls.append(entry) def _add_trait_get(self, obj, name, result): """ Add a call to a trait getter, with it's associated result and ID, to the current script. Return True if the get had side effects. """ self._start_script() side_effects = obj.trait(name).has_side_effects if side_effects is None: side_effects = False so = self._object_as_string(obj) if result is not None: self._save_result(result) self._calls.append( _ScriptTraitGet(so=so, name=name, result=result, has_side_effects=side_effects)) return side_effects def _add_trait_set(self, obj, name, value): """ Add a call to a trait setter, with it's associated value and ID, to the current script. """ self._start_script() so = self._object_as_string(obj) value = self._object_as_string(value) self._calls.append(_ScriptTraitSet(so=so, name=name, value=value)) def _unique_name(self, name, bind_policy): """ Return a name that is guaranteed to be unique according to the bind policy. """ # See if the name is already is use. bo = self._namespace.get(name) if bo is None: self._names[name] = 1 elif bind_policy == 'auto': suff = self._names[name] self._names[name] = suff + 1 name = '%s%d' % (name, suff) elif bind_policy == 'rebind': self._unbind(bo) else: raise NameError("\"%s\" is already bound to a scriptable object" % name) return name def _unbind(self, bo): """Unbind the given bound object.""" # Tell everybody it is no longer bound. Don't bother if it is a # factory because the corresponding bound event wouldn't have been # fired. if not isinstance(bo, _FactoryObject): self.bind_event = BindEvent(name=bo.name, obj=None) # Forget about it. del self._namespace[bo.name] bo.name = '' @staticmethod def _gc_script_obj(obj_ref): """ The callback invoked when a scriptable object is garbage collected. """ # Avoid recursive imports. from .package_globals import get_script_manager sm = get_script_manager() so = sm._so_by_ref[obj_ref] if so.name: sm._unbind(so) del sm._so_by_id[so.obj_id] del sm._so_by_ref[so.obj_ref] def _start_script(self): """ Save when a script recording is started. """ if len(self._calls) == 0: self._when_started = datetime.datetime.now().strftime('%c') def _object_as_string(self, obj): """ Convert an object to a string as it will appear in a script. An exception may be returned (not raised) if there was an error in the conversion. """ obj_id = id(obj) # See if the argument is the result of a previous call. nr, _ = self._results.get(obj_id, (None, None)) if nr is not None: if nr < 0: nr = self._next_result_nr self._next_result_nr += 1 # Key on the ID of the argument (which is hashable) rather than # the argument itself (which might not be). self._results[obj_id] = (nr, obj) return "r%d" % nr return self._scriptable_object_as_string(obj) def _scriptable_object_as_string(self, obj): """ Convert an object to a string as it will appear in a script. An exception may be returned (not raised) if there was an error in the conversion. """ obj_id = id(obj) # If it is a scriptable object we return the object and convert it to a # string later when we know it is really needed. so = self._so_by_id.get(obj_id) if so is not None: return so # Use the repr result if it doesn't appear to be the generic response, # ie. it doesn't contain its own address as a hex string. s = repr(obj) if hex(obj_id) not in s: return s # We don't know how to represent the argument as a string. This is # most likely because an appropriate __init__ hasn't been made # scriptable. We don't raise an exception until the user decides to # convert the calls to a script. return ValueError("unable to create a script representation of %s" % obj) def _save_result(self, result): """ Save the result of a call to a scriptable method so that it can be recognised later. """ if id(result) not in self._results: self._results[id(result)] = (-1, result) def _get_script(self): """ Convert the current list of calls to a script. """ # Handle the trivial case. if len(self._calls) == 0: return "" # Generate the header. header = "# Script generated %s" % self._when_started # Generate the calls. so_needed = [] calls = [] for call in self._calls: s = call.as_str(self, so_needed) if s: calls.append(s) calls = "\n".join(calls) # Generate the scriptable object constructors. types_needed = [] ctors = [] for so in so_needed: if so.explicitly_bound: continue so_type = so.scripted_type args = self.args_as_string_list(so.args, so.kwargs) ctors.append("%s = %s(%s)" % (so.name, so_type.__name__, ", ".join(args))) # See if a new import is needed. if so_type not in types_needed: types_needed.append(so_type) ctors = "\n".join(ctors) # Generate the import statements. imports = [] for so_type in types_needed: imports.append("from %s import %s" % (so_type.__module__, so_type.__name__)) imports = "\n".join(imports) return "\n\n".join([header, imports, ctors, calls]) + "\n"
class ProcessView(HasTraits): """A view object containing the process to optimise. This is a hierarchical construct consisting of execution layers containing data sources""" # ------------------- # Required Attributes # ------------------- #: The Process model model = Instance(Workflow) #: The Variable Names Registry variable_names_registry = Instance(VariableNamesRegistry) # ------------------ # Regular Attributes # ------------------ #: List of the data source's modelviews. #: Must be a list otherwise the tree editor will not consider it #: as a child. execution_layer_views = List(Instance(ExecutionLayerView)) #: The label to display in the list label = Str('Process') # --------------------- # Dependent Attributes # --------------------- #: Defines if the MCO is valid or not. Updated by #: :func:`verify_tree #: <force_wfmanager.ui.setup.workflow_tree.WorkflowTree.verify_tree>` valid = Bool(True) #: An error message for issues in this modelview. Updated by #: :func:`workflow_tree.WorkflowTree.verify_tree #: <force_wfmanager.ui.setup.workflow_tree.WorkflowTree.verify_tree>` error_message = Str() #: An event which runs a verification check on the current workflow when #: triggered. #: Listens to: :func:`execution_layer_views.verify_workflow_event` verify_workflow_event = Event() def __init__(self, model, *args, **kwargs): super(ProcessView, self).__init__(*args, **kwargs) # Assigns model after super instantiation in order to ensure # variable_names_registry has been assigned first self.model = model # ------------------- # Listeners # ------------------- @on_trait_change('model.execution_layers[]') def update_execution_layers_views(self): """Update the ExecutionLayer ModelViews when the model changes.""" self.execution_layer_views = [ ExecutionLayerView( model=execution_layer, layer_index=idx, variable_names_registry=self.variable_names_registry, label="Layer {}".format(idx)) for idx, execution_layer in enumerate(self.model.execution_layers) ] @on_trait_change('execution_layer_views.verify_workflow_event') def received_verify_request(self): """Fires :attr:`verify_workflow_event` when a data source contained in this execution layer fires its `verify_workflow_event` """ self.verify_workflow_event = True # ------------------- # Public Methods # ------------------- def add_execution_layer(self, execution_layer): """Adds a new empty execution layer""" self.model.execution_layers.append(execution_layer) def remove_execution_layer(self, layer): """Removes the execution layer from the model.""" self.model.execution_layers.remove(layer) self.verify_workflow_event = True # NOTE: Currently needed by TreeEditor as a reference point def remove_data_source(self, data_source): """Removes the data source from the model""" for execution_layer_view in self.execution_layer_views: if data_source in execution_layer_view.model.data_sources: execution_layer_view.remove_data_source(data_source)
class LRUCache(HasStrictTraits): """ A least-recently used cache. Items older than `size()` accesses are dropped from the cache. """ size = Int # Called with the key and value that was dropped from the cache cache_drop_callback = Callable # This event contains the set of cached cell keys whenever it changes updated = Event() _lock = Instance(RLock, args=()) _cache = Instance(OrderedDict) def __init__(self, size, **traits): self.size = size self._initialize_cache() super(LRUCache, self).__init__(**traits) def _initialize_cache(self): with self._lock: if self._cache is None: self._cache = OrderedDict() else: self._cache.clear() def _renew(self, key): with self._lock: r = self._cache.pop(key) self._cache[key] = r return r # ------------------------------------------------------------------------- # LRUCache interface # ------------------------------------------------------------------------- def __contains__(self, key): with self._lock: return key in self._cache def __len__(self): with self._lock: return len(self._cache) def __getitem__(self, key): with self._lock: return self._renew(key) def __setitem__(self, key, result): try: dropped = None with self._lock: self._cache[key] = result self._renew(key) if self.size < len(self._cache): dropped = self._cache.popitem(last=False) if dropped and self.cache_drop_callback is not None: self.cache_drop_callback(*dropped) finally: self.updated = self.keys() def get(self, key, default=None): try: return self[key] except KeyError: return default def items(self): with self._lock: return self._cache.items() def keys(self): with self._lock: return self._cache.keys() def values(self): with self._lock: return self._cache.values() def clear(self): with self._lock: self._initialize_cache() self.updated = []
class PythonEditor(MPythonEditor, Widget): """ The toolkit specific implementation of a PythonEditor. See the IPythonEditor interface for the API documentation. """ #### 'IPythonEditor' interface ############################################ dirty = Bool(False) path = Unicode show_line_numbers = Bool(True) #### Events #### changed = Event key_pressed = Event(KeyPressedEvent) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): super(PythonEditor, self).__init__(**traits) self.control = self._create_control(parent) ########################################################################### # 'PythonEditor' interface. ########################################################################### def load(self, path=None): """ Loads the contents of the editor. """ if path is None: path = self.path # We will have no path for a new script. if len(path) > 0: f = open(self.path, 'r') text = f.read() f.close() else: text = '' self.control.code.setPlainText(text) self.dirty = False def save(self, path=None): """ Saves the contents of the editor. """ if path is None: path = self.path f = file(path, 'w') f.write(self.control.code.toPlainText()) f.close() self.dirty = False def select_line(self, lineno): """ Selects the specified line. """ self.control.code.set_line_column(lineno, 0) self.control.code.moveCursor(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) ########################################################################### # Trait handlers. ########################################################################### def _path_changed(self): self._changed_path() def _show_line_numbers_changed(self): if self.control is not None: self.control.code.line_number_widget.setVisible( self.show_line_numbers) self.control.code.update_line_number_width() ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Creates the toolkit-specific control for the widget. """ self.control = control = AdvancedCodeWidget(parent) self._show_line_numbers_changed() # Install event filter to trap key presses. event_filter = PythonEditorEventFilter(self, self.control) self.control.installEventFilter(event_filter) self.control.code.installEventFilter(event_filter) # Connect signals for text changes. control.code.modificationChanged.connect(self._on_dirty_changed) control.code.textChanged.connect(self._on_text_changed) # Load the editor's contents. self.load() return control def _on_dirty_changed(self, dirty): """ Called whenever a change is made to the dirty state of the document. """ self.dirty = dirty def _on_text_changed(self): """ Called whenever a change is made to the text of the document. """ self.changed = True
class _ShellEditor(Editor): """ Base class for an editor that displays an interactive Python shell. """ #: An event fired to execute a command in the shell. command_to_execute = Event() #: An event fired whenver the user executes a command in the shell: command_executed = Event(Bool) #: Is the shell editor is scrollable? This value overrides the default. scrollable = True # ------------------------------------------------------------------------- # 'Editor' Interface # ------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ # Moving the import here, since PythonShell is implemented in the # Pyface backend packages, and we want to delay loading this toolkit # specific class until this editor is actually used. from pyface.python_shell import PythonShell locals = None value = self.value if self.factory.share and isinstance(value, dict): locals = value self._shell = shell = PythonShell(parent, locals=locals) self.control = shell.control if locals is None: object = self.object shell.bind("self", object) shell.observe(self.update_object, "command_executed", dispatch="ui") if not isinstance(value, dict): object.observe(self.update_any, dispatch="ui") else: self._base_locals = locals = {} for name in self._shell.interpreter().locals.keys(): locals[name] = None # Synchronize any editor events: self.sync_value(self.factory.command_to_execute, "command_to_execute", "from") self.sync_value(self.factory.command_executed, "command_executed", "to") self.set_tooltip() def update_object(self, event): """ Handles the user entering input data in the edit control. """ locals = self._shell.interpreter().locals base_locals = self._base_locals if base_locals is None: object = self.object for name in object.trait_names(): if name in locals: try: setattr(object, name, locals[name]) except: pass else: dic = self.value for name in locals.keys(): if name not in base_locals: try: dic[name] = locals[name] except: pass self.command_executed = True def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ if self.factory.share: value = self.value if isinstance(value, dict): self._shell.interpreter().locals = value else: locals = self._shell.interpreter().locals base_locals = self._base_locals if base_locals is None: object = self.object for name in object.trait_names(): locals[name] = getattr(object, name, None) else: dic = self.value for name, value in dic.items(): locals[name] = value def update_any(self, event): """ Updates the editor when the object trait changes externally to the editor. """ locals = self._shell.interpreter().locals if self._base_locals is None: locals[event.name] = event.new else: self.value[event.name] = event.new def dispose(self): """ Disposes of the contents of an editor. """ self._shell.observe(self.update_object, "command_executed", remove=True, dispatch="ui") if self._base_locals is None: self.object.observe(self.update_any, remove=True, dispatch="ui") super().dispose() def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ shell = self._shell try: history = prefs.get("history", []) history_index = prefs.get("history_index", -1) shell.set_history(history, history_index) except: pass def save_prefs(self): """ Returns any user preference information associated with the editor. """ history, history_index = self._shell.get_history() return {"history": history, "history_index": history_index} # ------------------------------------------------------------------------- # Private Interface # ------------------------------------------------------------------------- # Trait change handlers -------------------------------------------------- def _command_to_execute_fired(self, command): """ Handles the 'command_to_execute' trait being fired. """ # Show the command. A 'hidden' command should be executed directly on # the namespace trait! self._shell.execute_command(command, hidden=False)
class TabularAdapter(HasPrivateTraits): """ The base class for adapting list items to values that can be edited by a TabularEditor. """ # -- Public Trait Definitions --------------------------------------------- #: A list of columns that should appear in the table. Each entry can have #: one of two forms: ``string`` or ``(string, id)``, where ``string`` is #: the UI name of the column, and ``id`` is a value that identifies that #: column to the adapter. Normally this value is either a trait name or an #: index, but it can be any value that the adapter wants. If only #: ``string`` is specified, then ``id`` is the index of the ``string`` #: within :py:attr:`columns`. columns = List() #: Maps UI name of column to value identifying column to the adapter, if #: different. column_dict = Property() #: Specifies the default value for a new row. This will usually need to be #: overridden. default_value = Any("") #: The default text color for odd table rows. odd_text_color = Color(None, update=True) #: The default text color for even table rows. even_text_color = Color(None, update=True) #: The default text color for table rows. default_text_color = Color(None, update=True) #: The default background color for odd table rows. odd_bg_color = Color(None, update=True) #: The default background color for even table rows. even_bg_color = Color(None, update=True) #: The default background color for table rows. default_bg_color = Color(None, update=True) #: Horizontal alignment to use for a specified column. alignment = Enum("left", "center", "right") #: The Python format string to use for a specified column. format = Str("%s") #: Width of a specified column. width = Float(-1) #: Can the text value of each item be edited? can_edit = Bool(True) #: The value to be dragged for a specified row item. drag = Property #: Can any arbitrary value be dropped onto the tabular view. can_drop = Bool(False) #: Specifies where a dropped item should be placed in the table relative to #: the item it is dropped on. dropped = Enum("after", "before") #: The font for a row item. font = Font(None) #: The text color for a row item. text_color = Property #: The background color for a row item. bg_color = Property #: The name of the default image to use for column items. image = Str(None, update=True) #: The text of a row/column item. text = Property #: The content of a row/column item (may be any Python value). content = Property #: The tooltip information for a row/column item. tooltip = Str #: The context menu for a row/column item. menu = Any #: The context menu for column header. column_menu = Any #: List of optional delegated adapters. adapters = List(ITabularAdapter, update=True) # -- Traits Set by the Editor --------------------------------------------- #: The object whose trait is being edited. object = Instance(HasTraits) #: The name of the trait being edited. name = Str #: The row index of the current item being adapted. row = Int #: The column index of the current item being adapted. column = Int #: The current column id being adapted (if any). column_id = Any #: Current item being adapted. item = Any #: The current value (if any). value = Any # -- Private Trait Definitions -------------------------------------------- #: Cache of attribute handlers. cache = Any({}) #: Event fired when the cache is flushed. cache_flushed = Event(update=True) #: The mapping from column indices to column identifiers (defined by the #: :py:attr:`columns` trait). column_map = Property(depends_on="columns") #: The mapping from column indices to column labels (defined by the #: :py:attr:`columns` trait). label_map = Property(depends_on="columns") #: The name of the trait on a row item containing the value to use #: as a row label. If ``None``, the label will be the empty string. row_label_name = Either(None, Str) #: For each adapter, specifies the column indices the adapter handles. adapter_column_indices = Property(depends_on="adapters,columns") #: For each adapter, specifies the mapping from column index to column id. adapter_column_map = Property(depends_on="adapters,columns") # ------------------------------------------------------------------------- # TabularAdapter interface # ------------------------------------------------------------------------- def cleanup(self): """ Clean up the adapter to remove references to objects. """ self.trait_setq(object=None, item=None, value=None) # -- Adapter methods that are sensitive to item type ---------------------- def get_alignment(self, object, trait, column): """ Returns the alignment style to use for a specified column. The possible values that can be returned are: ``'left'``, ``'center'`` or ``'right'``. All table items share the same alignment for a specified column. """ return self._result_for("get_alignment", object, trait, 0, column) def get_width(self, object, trait, column): """ Returns the width to use for a specified column. If the value is <= 0, the column will have a *default* width, which is the same as specifying a width of 0.1. If the value is > 1.0, it is converted to an integer and the result is the width of the column in pixels. This is referred to as a *fixed width* column. If the value is a float such that 0.0 < value <= 1.0, it is treated as the *unnormalized fraction of the available space* that is to be assigned to the column. What this means requires a little explanation. To arrive at the size in pixels of the column at any given time, the editor adds together all of the *unnormalized fraction* values returned for all columns in the table to arrive at a total value. Each *unnormalized fraction* is then divided by the total to create a *normalized fraction*. Each column is then assigned an amount of space in pixels equal to the maximum of 30 or its *normalized fraction* multiplied by the *available space*. The *available space* is defined as the actual width of the table minus the width of all *fixed width* columns. Note that this calculation is performed each time the table is resized in the user interface, thus allowing columns of this type to increase or decrease their width dynamically, while leaving *fixed width* columns unchanged. """ return self._result_for("get_width", object, trait, 0, column) def get_can_edit(self, object, trait, row): """ Returns whether the user can edit a specified row. A ``True`` result indicates that the value can be edited, while a ``False`` result indicates that it cannot. """ return self._result_for("get_can_edit", object, trait, row, 0) def get_drag(self, object, trait, row): """ Returns the value to be *dragged* for a specified row. A result of ``None`` means that the item cannot be dragged. Note that the value returned does not have to be the actual row item. It can be any value that you want to drag in its place. In particular, if you want the drag target to receive a copy of the row item, you should return a copy or clone of the item in its place. Also note that if multiple items are being dragged, and this method returns ``None`` for any item in the set, no drag operation is performed. """ return self._result_for("get_drag", object, trait, row, 0) def get_can_drop(self, object, trait, row, value): """ Returns whether the specified ``value`` can be dropped on the specified row. A value of ``True`` means the ``value`` can be dropped; and a value of ``False`` indicates that it cannot be dropped. The result is used to provide the user positive or negative drag feedback while dragging items over the table. ``value`` will always be a single value, even if multiple items are being dragged. The editor handles multiple drag items by making a separate call to :py:meth:`get_can_drop` for each item being dragged. """ return self._result_for("get_can_drop", object, trait, row, 0, value) def get_dropped(self, object, trait, row, value): """ Returns how to handle a specified ``value`` being dropped on a specified row. The possible return values are: - ``'before'``: Insert the specified ``value`` before the dropped on item. - ``'after'``: Insert the specified ``value`` after the dropped on item. Note there is no result indicating *do not drop* since you will have already indicated that the ``object`` can be dropped by the result returned from a previous call to :py:meth:`get_can_drop`. """ return self._result_for("get_dropped", object, trait, row, 0, value) def get_font(self, object, trait, row, column=0): """ Returns the font to use for displaying a specified row or cell. A result of ``None`` means use the default font; otherwise a toolkit font object should be returned. Note that all columns for the specified table row will use the font value returned. """ return self._result_for("get_font", object, trait, row, column) def get_text_color(self, object, trait, row, column=0): """ Returns the text color to use for a specified row or cell. A result of ``None`` means use the default text color; otherwise a toolkit-compatible color should be returned. Note that all columns for the specified table row will use the text color value returned. """ return self._result_for("get_text_color", object, trait, row, column) def get_bg_color(self, object, trait, row, column=0): """ Returns the background color to use for a specified row or cell. A result of ``None`` means use the default background color; otherwise a toolkit-compatible color should be returned. Note that all columns for the specified table row will use the background color value returned. """ return self._result_for("get_bg_color", object, trait, row, column) def get_image(self, object, trait, row, column): """ Returns the image to display for a specified cell. A result of ``None`` means no image will be displayed in the specified table cell. Otherwise the result should either be the name of the image, or an :py:class:`~pyface.image_resource.ImageResource` object specifying the image to display. A name is allowed in the case where the image is specified in the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.images` trait. In that case, the name should be the same as the string specified in the :py:class:`~pyface.image_resource.ImageResource` constructor. """ return self._result_for("get_image", object, trait, row, column) def get_format(self, object, trait, row, column): """ Returns the Python formatting string to apply to the specified cell. The resulting of formatting with this string will be used as the text to display it in the table. The return can be any Python string containing exactly one old-style Python formatting sequence, such as ``'%.4f'`` or ``'(%5.2f)'``. """ return self._result_for("get_format", object, trait, row, column) def get_text(self, object, trait, row, column): """ Returns a string containing the text to display for a specified cell. If the underlying data representation for a specified item is not a string, then it is your responsibility to convert it to one before returning it as the result. """ return self._result_for("get_text", object, trait, row, column) def get_content(self, object, trait, row, column): """ Returns the content to display for a specified cell. """ return self._result_for("get_content", object, trait, row, column) def set_text(self, object, trait, row, column, text): """ Sets the value for the specified cell. This method is called when the user completes an editing operation on a table cell. The string specified by ``text`` is the value that the user has entered in the table cell. If the underlying data does not store the value as text, it is your responsibility to convert ``text`` to the correct representation used. """ self._result_for("set_text", object, trait, row, column, text) def get_tooltip(self, object, trait, row, column): """ Returns a string containing the tooltip to display for a specified cell. You should return the empty string if you do not wish to display a tooltip. """ return self._result_for("get_tooltip", object, trait, row, column) def get_menu(self, object, trait, row, column): """ Returns the context menu for a specified cell. """ return self._result_for("get_menu", object, trait, row, column) def get_column_menu(self, object, trait, row, column): """ Returns the context menu for a specified column. """ return self._result_for("get_column_menu", object, trait, row, column) # -- Adapter methods that are not sensitive to item type ------------------ def get_item(self, object, trait, row): """ Returns the specified row item. The value returned should be the value that exists (or *logically* exists) at the specified ``row`` in your data. If your data is not really a list or array, then you can just use ``row`` as an integer *key* or *token* that can be used to retrieve a corresponding item. The value of ``row`` will always be in the range: 0 <= row < ``len(object, trait)`` (i.e. the result returned by the adapter :py:meth:`len` method). The default implementation assumes the trait defined by ``object.trait`` is a *sequence* and attempts to return the value at index ``row``. If an error occurs, it returns ``None`` instead. This definition should work correctly for lists, tuples and arrays, or any other object that is indexable, but will have to be overridden for all other cases. """ try: return getattr(object, trait)[row] except: return None def len(self, object, trait): """ Returns the number of row items in the specified ``object.trait``. The result should be an integer greater than or equal to 0. The default implementation assumes the trait defined by ``object.trait`` is a *sequence* and attempts to return the result of calling ``len(object.trait)``. It will need to be overridden for any type of data which for which :py:func:`len` will not work. """ # Sometimes, during shutdown, the object has been set to None. if object is None: return 0 else: return len(getattr(object, trait)) def get_default_value(self, object, trait): """ Returns a new default value for the specified ``object.trait`` list. This method is called when *insert* or *append* operations are allowed and the user requests that a new item be added to the table. The result should be a new instance of whatever underlying representation is being used for table items. The default implementation simply returns the value of the adapter's :py:attr:`default_value` trait. """ return self.default_value def delete(self, object, trait, row): """ Deletes the specified row item. This method is only called if the *delete* operation is specified in the :py:class:`~traitsui.editors.tabular_editor.TabularEditor` :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.operation` trait, and the user requests that the item be deleted from the table. The adapter can still choose not to delete the specified item if desired, although that may prove confusing to the user. The default implementation assumes the trait defined by ``object.trait`` is a mutable sequence and attempts to perform a ``del object.trait[row]`` operation. """ del getattr(object, trait)[row] def insert(self, object, trait, row, value): """ Inserts ``value`` at the specified ``object.trait[row]`` index. The specified ``value`` can be: - An item being moved from one location in the data to another. - A new item created by a previous call to :py:meth:`~TabularAdapter.get_default_value`. - An item the adapter previously approved via a call to :py:meth:`~TabularAdapter.get_can_drop`. The adapter can still choose not to insert the item into the data, although that may prove confusing to the user. The default implementation assumes the trait defined by ``object.trait`` is a mutable sequence and attempts to perform an ``object.trait[row:row] = [value]`` operation. """ getattr(object, trait)[row:row] = [value] def get_column(self, object, trait, index): """ Returns the column id corresponding to a specified column index. """ self.object, self.name = object, trait return self.column_map[index] # -- Property Implementations --------------------------------------------- def _get_drag(self): return self.item def _get_text_color(self): if (self.row % 2) == 1: return self.even_text_color_ or self.default_text_color return self.odd_text_color or self.default_text_color_ def _get_bg_color(self): if (self.row % 2) == 1: return self.even_bg_color_ or self.default_bg_color_ return self.odd_bg_color or self.default_bg_color_ def _get_text(self): return self.get_format( self.object, self.name, self.row, self.column) % self.get_content( self.object, self.name, self.row, self.column) def _set_text(self, value): if isinstance(self.column_id, int): self.item[self.column_id] = self.value else: # Convert value to the correct trait type. try: trait_handler = self.item.trait(self.column_id).handler setattr( self.item, self.column_id, trait_handler.evaluate(self.value), ) except: setattr(self.item, self.column_id, value) def _get_content(self): if isinstance(self.column_id, int): return self.item[self.column_id] return getattr(self.item, self.column_id) # -- Property Implementations --------------------------------------------- @cached_property def _get_column_dict(self): cols = {} for i, value in enumerate(self.columns): if isinstance(value, six.string_types): cols.update({value: value}) else: cols.update({value[0]: value[1]}) return cols @cached_property def _get_column_map(self): map = [] for i, value in enumerate(self.columns): if isinstance(value, six.string_types): map.append(i) else: map.append(value[1]) return map def get_label(self, section, obj=None): """Override this method if labels will vary from object to object.""" return self.label_map[section] def get_row_label(self, section, obj=None): if self.row_label_name is None: return None rows = getattr(obj, self.name, None) if rows is None: return None item = rows[section] return getattr(item, self.row_label_name, None) @cached_property def _get_label_map(self): map = [] for i, value in enumerate(self.columns): if isinstance(value, six.string_types): map.append(value) else: map.append(value[0]) return map @cached_property def _get_adapter_column_indices(self): labels = self.label_map map = [] for adapter in self.adapters: indices = [] for label in adapter.columns: if not isinstance(label, six.string_types): label = label[0] indices.append(labels.index(label)) map.append(indices) return map @cached_property def _get_adapter_column_map(self): labels = self.label_map map = [] for adapter in self.adapters: mapping = {} for label in adapter.columns: id = None if not isinstance(label, six.string_types): label, id = label key = labels.index(label) if id is None: id = key mapping[key] = id map.append(mapping) return map # -- Private Methods ------------------------------------------------------ def _result_for(self, name, object, trait, row, column, value=None): """ Returns/Sets the value of the specified *name* attribute for the specified *object.trait[row].column* item. """ self.object = object self.name = trait self.row = row self.column = column self.column_id = column_id = self.column_map[column] self.value = value self.item = item = self.get_item(object, trait, row) item_class = item.__class__ key = "%s:%s:%d" % (item_class.__name__, name, column) handler = self.cache.get(key) if handler is not None: return handler() prefix = name[:4] trait_name = name[4:] for i, adapter in enumerate(self.adapters): if column in self.adapter_column_indices[i]: adapter.row = row adapter.item = item adapter.value = value adapter.column = column_id = self.adapter_column_map[i][column] if adapter.accepts: get_name = "%s_%s" % (column_id, trait_name) if adapter.trait(get_name) is not None: if prefix == "get_": handler = lambda: getattr( adapter.trait_set( row=self.row, column=column_id, item=self.item, ), get_name, ) else: handler = lambda: setattr( adapter.trait_set( row=self.row, column=column_id, item=self.item, ), get_name, self.value, ) if adapter.is_cacheable: break return handler() else: if item is not None and hasattr(item_class, "__mro__"): for klass in item_class.__mro__: handler = self._get_handler_for( "%s_%s_%s" % (klass.__name__, column_id, trait_name), prefix, ) or self._get_handler_for( "%s_%s" % (klass.__name__, trait_name), prefix) if handler is not None: break if handler is None: handler = self._get_handler_for( "%s_%s" % (column_id, trait_name), prefix) or self._get_handler_for(trait_name, prefix) self.cache[key] = handler return handler() def _get_handler_for(self, name, prefix): """ Returns the handler for a specified trait name (or None if not found). """ if self.trait(name) is not None: if prefix == "get_": return lambda: getattr(self, name) return lambda: setattr(self, name, self.value) return None @on_trait_change("columns,adapters.+update") def _flush_cache(self): """ Flushes the cache when the columns or any trait on any adapter changes. """ self.cache = {} self.cache_flushed = True
class Application(HasTraits): """ An extensible, pluggable, application. This class handles the common case for non-GUI applications, and it is intended to be subclassed to change start/stop behaviour etc. """ #### 'IApplication' interface ############################################# # The application's globally unique identifier. id = Str # The name of a directory (created for you) to which the application can # read and write non-user accessible data, i.e. configuration information, # preferences, etc. home = Str # The name of a directory (created for you upon access) to which the # application can read and write user-accessible data, e.g. projects # created by the user. user_data = Str # The root preferences node. preferences = Instance(IPreferences) #### Events #### # Fired when the application is starting. starting = VetoableEvent(ApplicationEvent) # Fired when all plugins have been started. started = Event(ApplicationEvent) # Fired when the application is stopping. stopping = VetoableEvent(ApplicationEvent) # Fired when all plugins have been stopped. stopped = Event(ApplicationEvent) #### 'IPluginManager' interface ########################################### #### Events #### # Fired when a plugin has been added. plugin_added = Delegate("plugin_manager", modify=True) # Fired when a plugin has been removed. plugin_removed = Delegate("plugin_manager", modify=True) #### 'Application' interface ############################################## # These traits allow application developers to build completely different # styles of extensible application. It allows Envisage to be used as a # framework for frameworks ;^) # # The extension registry. extension_registry = Instance(IExtensionRegistry) # The plugin manager (starts and stops plugins etc). plugin_manager = Instance(IPluginManager) # The service registry. service_registry = Instance(IServiceRegistry) #### Private interface #################################################### # The import manager. _import_manager = Instance(IImportManager, factory=ImportManager) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, plugins=None, **traits): """ Constructor. We allow the caller to specify an initial list of plugins, but the list itself is not part of the public API. To add and remove plugins after after construction, use the 'add_plugin' and 'remove_plugin' methods respectively. The application is also iterable, so to iterate over the plugins use 'for plugin in application: ...'. """ super(Application, self).__init__(**traits) # fixme: We have to initialize the application home here (i.e. we can't # wait until the 'home' trait is accessed) because the scoped # preferences uses 'ETSConfig.application' home as the name of the # default preferences file. self._initialize_application_home() # Set the default preferences node used by the preferences package. # This allows 'PreferencesHelper' and 'PreferenceBinding' instances to # be used as more convenient ways to access preferences. # # fixme: This is another sneaky global! set_default_preferences(self.preferences) # We allow the caller to specify an initial list of plugins, but the # list itself is not part of the public API. To add and remove plugins # after construction, use the 'add_plugin' and 'remove_plugin' methods # respectively. The application is also iterable, so to iterate over # the plugins use 'for plugin in application: ...'. if plugins is not None: for plugin in plugins: self.add_plugin(plugin) return ########################################################################### # 'IApplication' interface. ########################################################################### #### Trait initializers ################################################### def _home_default(self): """ Trait initializer. """ return ETSConfig.application_home def _user_data_default(self): """ Trait initializer. """ user_data = os.path.join(ETSConfig.user_data, self.id) # Make sure it exists! if not os.path.exists(user_data): os.makedirs(user_data) return user_data def _preferences_default(self): """ Trait initializer. """ return ScopedPreferences() #### Methods ############################################################## def run(self): """ Run the application. """ if self.start(): self.stop() return ########################################################################### # 'IExtensionRegistry' interface. ########################################################################### def add_extension_point_listener(self, listener, extension_point_id=None): """ Add a listener for extensions being added/removed. """ self.extension_registry.add_extension_point_listener( listener, extension_point_id) return def add_extension_point(self, extension_point): """ Add an extension point. """ self.extension_registry.add_extension_point(extension_point) return def get_extensions(self, extension_point_id): """ Return a list containing all contributions to an extension point. """ return self.extension_registry.get_extensions(extension_point_id) def get_extension_point(self, extension_point_id): """ Return the extension point with the specified Id. """ return self.extension_registry.get_extension_point(extension_point_id) def get_extension_points(self): """ Return all extension points that have been added to the registry. """ return self.extension_registry.get_extension_points() def remove_extension_point_listener(self, listener, extension_point_id=None): """ Remove a listener for extensions being added/removed. """ self.extension_registry.remove_extension_point_listener( listener, extension_point_id) return def remove_extension_point(self, extension_point_id): """ Remove an extension point. """ self.extension_registry.remove_extension_point(extension_point_id) return def set_extensions(self, extension_point_id, extensions): """ Set the extensions contributed to an extension point. """ self.extension_registry.set_extensions(extension_point_id, extensions) return ########################################################################### # 'IImportManager' interface. ########################################################################### def import_symbol(self, symbol_path): """ Import the symbol defined by the specified symbol path. """ return self._import_manager.import_symbol(symbol_path) ########################################################################### # 'IPluginManager' interface. ########################################################################### def __iter__(self): """ Return an iterator over the manager's plugins. """ return iter(self.plugin_manager) def add_plugin(self, plugin): """ Add a plugin to the manager. """ self.plugin_manager.add_plugin(plugin) return def get_plugin(self, plugin_id): """ Return the plugin with the specified Id. """ return self.plugin_manager.get_plugin(plugin_id) def remove_plugin(self, plugin): """ Remove a plugin from the manager. """ self.plugin_manager.remove_plugin(plugin) return def start(self): """ Start the plugin manager. Returns True unless the start was vetoed. """ # fixme: This method is notionally on the 'IPluginManager' interface # but that interface knows nothing about the vetoable events etc and # hence doesn't have a return value. logger.debug("---------- application starting ----------") # Lifecycle event. self.starting = event = self._create_application_event() if not event.veto: # Start the plugin manager (this starts all of the manager's # plugins). self.plugin_manager.start() # Lifecycle event. self.started = self._create_application_event() logger.debug("---------- application started ----------") else: logger.debug("---------- application start vetoed ----------") return not event.veto def start_plugin(self, plugin=None, plugin_id=None): """ Start the specified plugin. """ return self.plugin_manager.start_plugin(plugin, plugin_id) def stop(self): """ Stop the plugin manager. Returns True unless the stop was vetoed. """ # fixme: This method is notionally on the 'IPluginManager' interface # but that interface knows nothing about the vetoable events etc and # hence doesn't have a return value. logger.debug("---------- application stopping ----------") # Lifecycle event. self.stopping = event = self._create_application_event() if not event.veto: # Stop the plugin manager (this stops all of the manager's # plugins). self.plugin_manager.stop() # Save all preferences. self.preferences.save() # Lifecycle event. self.stopped = self._create_application_event() logger.debug("---------- application stopped ----------") else: logger.debug("---------- application stop vetoed ----------") return not event.veto def stop_plugin(self, plugin=None, plugin_id=None): """ Stop the specified plugin. """ return self.plugin_manager.stop_plugin(plugin, plugin_id) ########################################################################### # 'IServiceRegistry' interface. ########################################################################### def get_required_service(self, protocol, query="", minimize="", maximize=""): """ Return the service that matches the specified query. Raise a 'NoSuchServiceError' exception if no such service exists. """ service = self.service_registry.get_required_service( protocol, query, minimize, maximize) return service def get_service(self, protocol, query="", minimize="", maximize=""): """ Return at most one service that matches the specified query. """ service = self.service_registry.get_service(protocol, query, minimize, maximize) return service def get_service_from_id(self, service_id): """ Return the service with the specified id. """ return self.service_registry.get_service_from_id(service_id) def get_service_properties(self, service_id): """ Return the dictionary of properties associated with a service. """ return self.service_registry.get_service_properties(service_id) def get_services(self, protocol, query="", minimize="", maximize=""): """ Return all services that match the specified query. """ services = self.service_registry.get_services(protocol, query, minimize, maximize) return services def register_service(self, protocol, obj, properties=None): """ Register a service. """ service_id = self.service_registry.register_service( protocol, obj, properties) return service_id def set_service_properties(self, service_id, properties): """ Set the dictionary of properties associated with a service. """ self.service_registry.set_service_properties(service_id, properties) return def unregister_service(self, service_id): """ Unregister a service. """ self.service_registry.unregister_service(service_id) return ########################################################################### # 'Application' interface. ########################################################################### #### Trait initializers ################################################### def _extension_registry_default(self): """ Trait initializer. """ # Do the import here to emphasize the fact that this is just the # default implementation and that the application developer is free # to override it! from .plugin_extension_registry import PluginExtensionRegistry return PluginExtensionRegistry(plugin_manager=self) def _plugin_manager_default(self): """ Trait initializer. """ # Do the import here to emphasize the fact that this is just the # default implementation and that the application developer is free # to override it! from .plugin_manager import PluginManager return PluginManager(application=self) def _service_registry_default(self): """ Trait initializer. """ # Do the import here to emphasize the fact that this is just the # default implementation and that the application developer is free # to override it! from .service_registry import ServiceRegistry return ServiceRegistry() ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ # fixme: We have this to make it easier to assign a new plugin manager # at construction time due to the fact that the plugin manager needs a # reference to the application and vice-versa, e.g. we can do # # application = Application(plugin_manager=EggPluginManager()) # # If we didn't have this then we would have to do this:- # # application = Application() # application.plugin_manager = EggPluginManager(application=application) # # Of course, it would be better if the plugin manager didn't require a # reference to the application at all (it currently uses it to set the # 'application' trait of plugin instances - but that is only done for the # same reason as this (i.e. it is nice to be able to pass plugins into the # application constructor). def _plugin_manager_changed(self, trait_name, old, new): """ Static trait change handler. """ if old is not None: old.application = None if new is not None: new.application = self return #### Methods ############################################################## def _create_application_event(self): """ Create an application event. """ return ApplicationEvent(application=self) def _initialize_application_home(self): """ Initialize the application home directory. """ ETSConfig.application_home = os.path.join(ETSConfig.application_data, self.id) # Make sure it exists! if not os.path.exists(ETSConfig.application_home): os.makedirs(ETSConfig.application_home) return
class TableEditor(Editor, BaseTableEditor): """ Editor that presents data in a table. Optionally, tables can have a set of filters that reduce the set of data displayed, according to their criteria. """ # ------------------------------------------------------------------------- # Trait definitions: # ------------------------------------------------------------------------- #: The table view control associated with the editor: table_view = Any() def _table_view_default(self): return TableView(editor=self) #: A wrapper around the source model which provides filtering and sorting: model = Instance(SortFilterTableModel) def _model_default(self): return SortFilterTableModel(editor=self) #: The table model associated with the editor: source_model = Instance(TableModel) def _source_model_default(self): return TableModel(editor=self) #: The set of columns currently defined on the editor: columns = List(TableColumn) #: The currently selected row(s), column(s), or cell(s). selected = Any() #: The current selected row selected_row = Property(Any, depends_on="selected") selected_indices = Property(Any, depends_on="selected") #: Current filter object (should be a TableFilter or callable or None): filter = Any() #: The indices of the table items currently passing the table filter: filtered_indices = List(Int) #: Current filter summary message filter_summary = Str("All items") #: Update the filtered contents. update_filter = Event() #: The event fired when a cell is clicked on: click = Event() #: The event fired when a cell is double-clicked on: dclick = Event() #: The Traits UI associated with the table editor toolbar: toolbar_ui = Instance(UI) #: The context menu associated with empty space in the table empty_menu = Instance(QtGui.QMenu) #: The context menu associated with the vertical header header_menu = Instance(QtGui.QMenu) #: The context menu actions for moving rows up and down header_menu_up = Instance(QtGui.QAction) header_menu_down = Instance(QtGui.QAction) #: The index of the row that was last right clicked on its vertical header header_row = Int() #: Whether to auto-size the columns or not. auto_size = Bool(False) #: Dictionary mapping image names to QIcons images = Any({}) #: Dictionary mapping ImageResource objects to QIcons image_resources = Any({}) #: An image being converted: image = Image def init(self, parent): """Finishes initializing the editor by creating the underlying toolkit widget.""" factory = self.factory self.filter = factory.filter columns = factory.columns[:] if (len(columns) == 0) and (len(self.value) > 0): columns = [ ObjectColumn(name=name) for name in self.value[0].editable_traits() ] self.columns = columns if factory.table_view_factory is not None: self.table_view = factory.table_view_factory(editor=self) if factory.source_model_factory is not None: self.source_model = factory.source_model_factory(editor=self) if factory.model_factory is not None: self.model = factory.model_factory(editor=self) # Create the table view and model self.model.setDynamicSortFilter(True) self.model.setSourceModel(self.source_model) self.table_view.setModel(self.model) # Create the vertical header context menu and connect to its signals self.header_menu = QtGui.QMenu(self.table_view) insertable = factory.row_factory is not None if factory.editable: if insertable: action = self.header_menu.addAction("Insert new item") action.triggered.connect(self._on_context_insert) if factory.deletable: action = self.header_menu.addAction("Delete item") action.triggered.connect(self._on_context_remove) if factory.reorderable: if factory.editable and (insertable or factory.deletable): self.header_menu.addSeparator() self.header_menu_up = self.header_menu.addAction("Move item up") self.header_menu_up.triggered.connect(self._on_context_move_up) self.header_menu_down = self.header_menu.addAction( "Move item down") self.header_menu_down.triggered.connect(self._on_context_move_down) # Create the empty space context menu and connect its signals self.empty_menu = QtGui.QMenu(self.table_view) action = self.empty_menu.addAction("Add new item") action.triggered.connect(self._on_context_append) # When sorting is enabled, the first column is initially displayed with # the triangle indicating it is the sort index, even though no sorting # has actually been done. Sort here for UI/model consistency. if self.factory.sortable and not self.factory.reorderable: self.model.sort(0, QtCore.Qt.AscendingOrder) # Connect to the mode specific selection handler and select the first # row/column/cell. Do this before creating the edit_view to make sure # that it has a valid item to use when constructing its view. smodel = self.table_view.selectionModel() mode_slot = getattr(self, "_on_%s_selection" % factory.selection_mode) smodel.selectionChanged.connect(mode_slot) self.table_view.setCurrentIndex(self.model.index(0, 0)) # Create the toolbar if necessary if factory.show_toolbar and len(factory.filters) > 0: main_view = QtGui.QWidget() layout = QtGui.QVBoxLayout(main_view) layout.setContentsMargins(0, 0, 0, 0) self.toolbar_ui = self.edit_traits( parent=parent, kind="subpanel", view=View( Group( Item("filter{View}", editor=factory._filter_editor), Item("filter_summary{Results}", style="readonly"), spring, orientation="horizontal", ), resizable=True, ), ) self.toolbar_ui.parent = self.ui layout.addWidget(self.toolbar_ui.control) layout.addWidget(self.table_view) else: main_view = self.table_view # Create auxiliary editor and encompassing splitter if necessary mode = factory.selection_mode if (factory.edit_view == " ") or mode not in {"row", "rows"}: self.control = main_view else: if factory.orientation == "horizontal": self.control = QtGui.QSplitter(QtCore.Qt.Horizontal) else: self.control = QtGui.QSplitter(QtCore.Qt.Vertical) self.control.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.control.addWidget(main_view) self.control.setStretchFactor(0, 2) # Create the row editor below the table view editor = InstanceEditor(view=factory.edit_view, kind="subpanel") self._ui = self.edit_traits( parent=self.control, kind="subpanel", view=View( Item( "selected_row", style="custom", editor=editor, show_label=False, resizable=True, width=factory.edit_view_width, height=factory.edit_view_height, ), resizable=True, handler=factory.edit_view_handler, ), ) self._ui.parent = self.ui self.control.addWidget(self._ui.control) self.control.setStretchFactor(1, 1) # Connect to the click and double click handlers self.table_view.clicked.connect(self._on_click) self.table_view.doubleClicked.connect(self._on_dclick) # Make sure we listen for 'items' changes as well as complete list # replacements self.context_object.on_trait_change(self.update_editor, self.extended_name + "_items", dispatch="ui") # Listen for changes to traits on the objects in the list self.context_object.on_trait_change(self.refresh_editor, self.extended_name + ".-", dispatch="ui") # Listen for changes on column definitions self.on_trait_change(self._update_columns, "columns", dispatch="ui") self.on_trait_change(self._update_columns, "columns_items", dispatch="ui") # Set up the required externally synchronized traits is_list = mode in ("rows", "columns", "cells") self.sync_value(factory.click, "click", "to") self.sync_value(factory.dclick, "dclick", "to") self.sync_value(factory.columns_name, "columns", is_list=True) self.sync_value(factory.selected, "selected", is_list=is_list) self.sync_value(factory.selected_indices, "selected_indices", is_list=is_list) self.sync_value(factory.filter_name, "filter", "from") self.sync_value(factory.filtered_indices, "filtered_indices", "to") self.sync_value(factory.update_filter_name, "update_filter", "from") self.auto_size = self.factory.auto_size # Initialize the ItemDelegates for each column self._update_columns() def dispose(self): """ Disposes of the contents of an editor.""" self.model.beginResetModel() self.model.endResetModel() # Make sure that the auxiliary UIs are properly disposed if self.toolbar_ui is not None: self.toolbar_ui.dispose() if self._ui is not None: self._ui.dispose() # Remove listener for 'items' changes on object trait self.context_object.on_trait_change(self.update_editor, self.extended_name + "_items", remove=True) # Remove listener for changes to traits on the objects in the list self.context_object.on_trait_change(self.refresh_editor, self.extended_name + ".-", remove=True) # Remove listeners for column definition changes self.on_trait_change(self._update_columns, "columns", remove=True) self.on_trait_change(self._update_columns, "columns_items", remove=True) super(TableEditor, self).dispose() def update_editor(self): """Updates the editor when the object trait changes externally to the editor.""" if self._no_notify: return self.table_view.setUpdatesEnabled(False) try: filtering = (len(self.factory.filters) > 0 or self.filter is not None) if filtering: self._update_filtering() # invalidate the model, but do not reset it. Resetting the model # may cause problems if the selection sync'ed traits are being used # externally to manage the selections self.model.invalidate() self.table_view.resizeColumnsToContents() if self.auto_size: self.table_view.resizeRowsToContents() finally: self.table_view.setUpdatesEnabled(True) def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ header = self.table_view.horizontalHeader() if header is not None and "column_state" in prefs: header.restoreState(prefs["column_state"]) def save_prefs(self): """ Returns any user preference information associated with the editor. """ prefs = {} header = self.table_view.horizontalHeader() if header is not None: prefs["column_state"] = header.saveState().data() return prefs def refresh_editor(self): """Requests that the underlying table widget to redraw itself.""" self.table_view.viewport().update() def create_new_row(self): """Creates a new row object using the provided factory.""" factory = self.factory kw = factory.row_factory_kw.copy() if "__table_editor__" in kw: kw["__table_editor__"] = self return self.ui.evaluate(factory.row_factory, *factory.row_factory_args, **kw) def items(self): """Returns the raw list of model objects.""" items = self.value if not isinstance(items, SequenceTypes): items = [items] if self.factory and self.factory.reverse: items = ReversedList(items) return items def callx(self, func, *args, **kw): """Call a function without notifying the underlying table view or model.""" old = self._no_notify self._no_notify = True try: func(*args, **kw) finally: self._no_notify = old def setx(self, **keywords): """Set one or more attributes without notifying the underlying table view or model.""" old = self._no_notify self._no_notify = True try: for name, value in keywords.items(): setattr(self, name, value) finally: self._no_notify = old def set_selection(self, objects=[], notify=True): """Sets the current selection to a set of specified objects.""" if not isinstance(objects, list): objects = [objects] mode = self.factory.selection_mode indexes = [] flags = QtGui.QItemSelectionModel.ClearAndSelect # In the case of row or column selection, we need a dummy value for the # other dimension that has not been filtered. source_index = self.model.mapToSource(self.model.index(0, 0)) source_row, source_column = source_index.row(), source_index.column() # Selection mode is 'row' or 'rows' if mode.startswith("row"): flags |= QtGui.QItemSelectionModel.Rows items = self.items() for obj in objects: try: row = items.index(obj) except ValueError: continue indexes.append(self.source_model.index(row, source_column)) # Selection mode is 'column' or 'columns' elif mode.startswith("column"): flags |= QtGui.QItemSelectionModel.Columns for name in objects: column = self._column_index_from_name(name) if column != -1: indexes.append(self.source_model.index(source_row, column)) # Selection mode is 'cell' or 'cells' else: items = self.items() for obj, name in objects: try: row = items.index(obj) except ValueError: continue column = self._column_index_from_name(name) if column != -1: indexes.append(self.source_model.index(row, column)) # Perform the selection so that only one signal is emitted selection = QtGui.QItemSelection() smodel = self.table_view.selectionModel() if smodel is None: # guard against selection during tear-down return for index in indexes: index = self.model.mapFromSource(index) if index.isValid(): smodel.setCurrentIndex(index, QtGui.QItemSelectionModel.NoUpdate) selection.select(index, index) smodel.blockSignals(not notify) try: if len(selection.indexes()): smodel.clear() smodel.select(selection, flags) else: smodel.clear() finally: smodel.blockSignals(False) self.refresh_editor() # ------------------------------------------------------------------------- # Private methods: # ------------------------------------------------------------------------- def _column_index_from_name(self, name): """Returns the index of the column with the given name or -1 if no column exists with that name.""" for i, column in enumerate(self.columns): if name == column.name: return i return -1 def _customize_filters(self, filter): """Allows the user to customize the current set of table filters.""" filter_editor = TableFilterEditor(editor=self) ui = filter_editor.edit_traits(parent=self.control) if ui.result: self.factory.filters = filter_editor.templates self.filter = filter_editor.selected_filter else: self.setx(filter=filter) def _update_filtering(self): """Update the filter summary and the filtered indices.""" items = self.items() num_items = len(items) f = self.filter if f is None: self._filtered_cache = None self.filtered_indices = list(range(num_items)) self.filter_summary = "All %i items" % num_items else: if not callable(f): f = f.filter self._filtered_cache = fc = [f(item) for item in items] self.filtered_indices = fi = [i for i, ok in enumerate(fc) if ok] self.filter_summary = "%i of %i items" % (len(fi), num_items) def _add_image(self, image_resource): """ Adds a new image to the image map. """ image = image_resource.create_icon() self.image_resources[image_resource] = image self.images[image_resource.name] = image return image def _get_image(self, image): """ Converts a user specified image to a QIcon. """ if isinstance(image, str): self.image = image image = self.image if isinstance(image, ImageResource): result = self.image_resources.get(image) if result is not None: return result return self._add_image(image) return self.images.get(image) # -- Trait Property getters/setters --------------------------------------- @cached_property def _get_selected_row(self): """Gets the selected row, or the first row if multiple rows are selected.""" mode = self.factory.selection_mode if mode.startswith("column"): return None elif mode == "row": return self.selected try: if mode == "rows": return self.selected[0] elif mode == "cell": return self.selected[0] elif mode == "cells": return self.selected[0][0] except IndexError: return None @cached_property def _get_selected_indices(self): """Gets the row,column indices which match the selected trait""" selection_items = self.table_view.selectionModel().selection() indices = self.model.mapSelectionToSource(selection_items).indexes() if self.factory.selection_mode.startswith("row"): indices = sorted(set(index.row() for index in indices)) elif self.factory.selection_mode.startswith("column"): indices = sorted(set(index.column() for index in indices)) else: indices = [(index.row(), index.column()) for index in indices] if self.factory.selection_mode in {"rows", "columns", "cells"}: return indices elif len(indices) > 0: return indices[0] else: return -1 def _set_selected_indices(self, indices): if not isinstance(indices, list): indices = [indices] selected = [] if self.factory.selection_mode.startswith("row"): for row in indices: selected.append(self.value[row]) elif self.factory.selection_mode.startswith("column"): for col in indices: selected.append(self.columns[col].name) else: for row, col in indices: selected.append((self.value[row], self.columns[col].name)) self.selected = selected self.set_selection(self.selected, False) # -- Trait Change Handlers ------------------------------------------------ def _filter_changed(self, old_filter, new_filter): """Handles the current filter being changed.""" if not self._no_notify: if new_filter is customize_filter: do_later(self._customize_filters, old_filter) else: self._update_filtering() self.model.invalidate() self.set_selection(self.selected) def _update_columns(self): """Handle the column list being changed.""" self.table_view.setItemDelegate(TableDelegate(self.table_view)) for i, column in enumerate(self.columns): if column.renderer: self.table_view.setItemDelegateForColumn(i, column.renderer) self.model.invalidate() self.table_view.resizeColumnsToContents() if self.auto_size: self.table_view.resizeRowsToContents() def _selected_changed(self, new): """Handle the selected row/column/cell being changed externally.""" if not self._no_notify: self.set_selection(self.selected, notify=False) def _update_filter_changed(self): """ The filter has changed internally. """ self._filter_changed(self.filter, self.filter) # -- Event Handlers ------------------------------------------------------- def _on_row_selection(self, added, removed): """Handle the row selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedRows() if len(indexes): index = self.model.mapToSource(indexes[0]) selected = items[index.row()] else: selected = None self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_rows_selection(self, added, removed): """Handle the rows selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedRows() selected = [ items[self.model.mapToSource(index).row()] for index in indexes ] self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_column_selection(self, added, removed): """Handle the column selection being changed.""" indexes = self.table_view.selectionModel().selectedColumns() if len(indexes): index = self.model.mapToSource(indexes[0]) selected = self.columns[index.column()].name else: selected = "" self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_columns_selection(self, added, removed): """Handle the columns selection being changed.""" indexes = self.table_view.selectionModel().selectedColumns() selected = [ self.columns[self.model.mapToSource(index).column()].name for index in indexes ] self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_cell_selection(self, added, removed): """Handle the cell selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedIndexes() if len(indexes): index = self.model.mapToSource(indexes[0]) obj = items[index.row()] column_name = self.columns[index.column()].name else: obj = None column_name = "" selected = (obj, column_name) self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_cells_selection(self, added, removed): """Handle the cells selection being changed.""" items = self.items() indexes = self.table_view.selectionModel().selectedIndexes() selected = [] for index in indexes: index = self.model.mapToSource(index) obj = items[index.row()] column_name = self.columns[index.column()].name selected.append((obj, column_name)) self.setx(selected=selected) self.ui.evaluate(self.factory.on_select, self.selected) def _on_click(self, index): """Handle a cell being clicked.""" index = self.model.mapToSource(index) column = self.columns[index.column()] obj = self.items()[index.row()] # Fire the same event on the editor after mapping it to a model object # and column name: self.click = (obj, column) # Invoke the column's click handler: column.on_click(obj) def _on_dclick(self, index): """Handle a cell being double clicked.""" index = self.model.mapToSource(index) column = self.columns[index.column()] obj = self.items()[index.row()] # Fire the same event on the editor after mapping it to a model object # and column name: self.dclick = (obj, column) # Invoke the column's double-click handler: column.on_dclick(obj) def _on_context_insert(self): """Handle 'insert item' being selected from the header context menu.""" self.model.insertRow(self.header_row) def _on_context_append(self): """Handle 'add item' being selected from the empty space context menu.""" self.model.insertRow(self.model.rowCount()) def _on_context_remove(self): """Handle 'remove item' being selected from the header context menu.""" self.model.removeRow(self.header_row) def _on_context_move_up(self): """Handle 'move up' being selected from the header context menu.""" self.model.moveRow(self.header_row, self.header_row - 1) def _on_context_move_down(self): """Handle 'move down' being selected from the header context menu.""" self.model.moveRow(self.header_row, self.header_row + 1)
class IEditorAreaPane(ITaskPane): """ A central pane that contains tabbed editors. There are currently two implementations of this interface in Tasks. EditorAreaPane provides a simple, tabbed editor area. AdvancedEditorAreaPane additionally permits arbitrary splitting of the editor area so that editors can be displayed side-by-side. """ #### 'IEditorAreaPane' interface ########################################## # The currently active editor. active_editor = Instance(IEditor) # The list of all the visible editors in the pane. editors = List(IEditor) # A list of extensions for file types to accept via drag and drop. # Note: This functionality is provided because it is very common, but drag # and drop support is in general highly toolkit-specific. If more # sophisticated support is required, subclass an editor area implementation. file_drop_extensions = List(Str) # A file with a supported extension was dropped into the editor area. file_dropped = Event(File) # Whether to hide the tab bar when there is only a single editor. hide_tab_bar = Bool(False) ########################################################################### # 'IEditorAreaPane' interface. ########################################################################### def activate_editor(self, editor): """ Activates the specified editor in the pane. """ def add_editor(self, editor): """ Adds an editor to the pane. """ def create_editor(self, obj, factory=None): """ Creates an editor for an object. If a factory is specified, it will be used instead of the editor factory registry. Otherwise, this method will return None if a suitable factory cannot be found in the registry. Note that the editor is not added to the pane. """ def edit(self, obj, factory=None, use_existing=True): """ Edit an object. This is a convenience method that creates and adds an editor for the specified object. If 'use_existing' is set and the object is already being edited, then that editor will be activated and a new editor will not be created. Returns the (possibly new) editor for the object. """ def get_editor(self, obj): """ Returns the editor for an object. Returns None if the object is not being edited. """ def get_factory(self, obj): """ Returns an editor factory suitable for editing an object. Returns None if there is no such editor factory. """ def register_factory(self, factory, filter): """ Registers a factory for creating editors. The 'factory' parameter is a callabe of form: callable(editor_area=editor_area, obj=obj) -> IEditor Often, factory will be a class that provides the 'IEditor' interface. The 'filter' parameter is a callable of form: callable(obj) -> bool that indicates whether the editor factory is suitable for an object. If multiple factories apply to a single object, it is undefined which factory is used. On the other hand, multiple filters may be registered for a single factory, in which case only one must apply for the factory to be selected. """ def remove_editor(self, editor): """ Removes an editor from the pane. """ def unregister_factory(self, factory): """ Unregisters a factory for creating editors.
class TreeModel(HasTraits): """ Model for tree views. """ #### 'TreeModel' interface ################################################ # The root of the model. root = Any # Fired when nodes in the tree have changed in some way that affects their # appearance but NOT their structure or position in the tree. nodes_changed = Event(NodeEvent) # Fired when nodes have been inserted into the tree. nodes_inserted = Event(NodeEvent) # Fired when nodes have been removed from the tree. nodes_removed = Event(NodeEvent) # Fired when nodes have been replaced in the tree. nodes_replaced = Event(NodeEvent) # Fire when the structure of the tree has changed DRASTICALLY from a given # node down. structure_changed = Event(NodeEvent) ######################################################################### # 'TreeModel' interface. ######################################################################### def has_children(self, node): """ Returns True if a node has children, otherwise False. This method is provided in case the model has an efficient way to determine whether or not a node has any children without having to actually get the children themselves. """ raise NotImplementedError def get_children(self, node): """ Returns the children of a node. """ raise NotImplementedError def get_drag_value(self, node): """ Get the value that is dragged for a node. By default the drag value is the node itself. """ return node def can_drop(self, node, obj): """ Returns True if a node allows an object to be dropped onto it. """ return False def drop(self, node, obj): """ Drops an object onto a node. """ raise NotImplementedError def get_image(self, node, selected, expanded): """ Returns the label image for a node. Return None (the default) if no image is required. """ return None def get_key(self, node): """ Generate a unique key for a node. """ try: key = hash(node) except: key = id(node) return key def get_selection_value(self, node): """ Get the value that is used when a node is selected. By default the selection value is the node itself. """ return node def get_text(self, node): """ Returns the label text for a node. Return None if no text is required. By default we return 'str(node)'. """ return str(node) def can_set_text(self, node, text): """ Returns True if the node's label can be set. """ return len(text.strip()) > 0 def set_text(self, node, text): """ Sets the label text for a node. """ pass def is_collapsible(self, node): """ Returns True if the node is collapsible, otherwise False. """ return True def is_draggable(self, node): """ Returns True if the node is draggable, otherwise False. """ return True def is_editable(self, node): """ Returns True if the node is editable, otherwise False. If the node is editable, its text can be set via the UI. """ return False def is_expandable(self, node): """ Returns True if the node is expandanble, otherwise False. """ return True def add_listener(self, node): """ Adds a listener for changes to a node. """ pass def remove_listener(self, node): """ Removes a listener for changes to a node. """ pass def fire_nodes_changed(self, node, children): """ Fires the nodes changed event. """ self.nodes_changed = NodeEvent(node=node, children=children) return def fire_nodes_inserted(self, node, children): """ Fires the nodes inserted event. """ self.nodes_inserted = NodeEvent(node=node, children=children) return def fire_nodes_removed(self, parent, children): """ Fires the nodes removed event. """ self.nodes_removed = NodeEvent(node=node, children=children) return def fire_nodes_replaced(self, node, old_children, new_children): """ Fires the nodes removed event. """ self.nodes_replaced = NodeEvent( node=node, old_children=old_children, children=new_children ) return def fire_structure_changed(self, node): """ Fires the structure changed event. """ self.structure_changed = NodeEvent(node=node) return