class ObjectWithEqualityComparisonMode(HasTraits): """ Class for supporting TestHasTraitsHelpersWarning """ list_values = List(comparison_mode=2) dict_values = Dict(comparison_mode=2) set_values = Set(comparison_mode=2) property_list = Property(List(comparison_mode=2)) container_in_union = Union( None, Set(comparison_mode=1), comparison_mode=2, )
class Foo(HasTraits): alist = List([1, 2, 3]) adict = Dict({'red': 255, 'blue': 0, 'green': 127}) aset = Set({1, 2, 3}) @on_trait_change(["alist_items", "adict_items", "aset_items"]) def _receive_events(self, event): self.event = event
class ObjectWithEqualityComparisonMode(HasTraits): """ Class for supporting TestHasTraitsHelpersComparisonMode """ list_values = List(comparison_mode=ComparisonMode.equality) dict_values = Dict(comparison_mode=ComparisonMode.equality) set_values = Set(comparison_mode=ComparisonMode.equality) number = Any(comparison_mode=ComparisonMode.equality) calculated = Property(depends_on="number") def _get_calculated(self): return None
class TraitModel(HasTraits): value = Float value_delegate = Str() value_subscribe = Str() value_update = Str() value_simple = Str('simple_text') value_notify = Event() list_values = List(Str) dict_values = Dict(Str, Str) set_values = Set(Str) property_value = Property(depends_on='value') typed_property_value = Property(Float, depends_on='value') collection_property_value = Property(List, depends_on='value') def _get_property_value(self): return self.value def _get_typed_property_value(self): return self.value def _get_collection_property_value(self): value = self.value return [value, value * 10]
class ResizeTool(ValueDragTool): """ Generic tool for resizing a component """ # Should the resized component be raised to the top of its container's # list of components? This is only recommended for overlaying containers # and canvases, but generally those are the only ones in which the # ResizeTool will be useful. auto_raise = Bool(True) #: the hotspots which are active for this tool hotspots = Set(hotspot_trait) #: the distance in pixels from a hotspot required to register a hit threshhold = Int(10) #: the minimum bounds that we can resize to minimum_bounds = bounds_trait #: the hotspot that started the drag _selected_hotspot = hotspot_trait # 'ValueDragTool' Interface ############################################## def get_value(self): if self.component is not None: c = self.component return c.position[:], c.bounds[:] def set_delta(self, value, delta_x, delta_y): if self.component is not None: c = self.component position, bounds = value x, y = position width, height = bounds min_width, min_height = self.minimum_bounds edges = self._selected_hotspot.split() if "left" in edges: if delta_x >= width - min_width: delta_x = width - min_width c.x = x + delta_x c.width = width - delta_x if "right" in edges: if delta_x <= -width + min_width: delta_x = -width + min_width c.width = width + delta_x if "bottom" in edges: if delta_y >= height - min_height: delta_y = height - min_height c.y = y + delta_y c.height = height - delta_y if "top" in edges: if delta_y <= -height + min_height: delta_y = -height + min_height c.height = height + delta_y c._layout_needed = True c.request_redraw() # 'DragTool' Interface ################################################### def is_draggable(self, x, y): return self._find_hotspot(x, y) in self.hotspots def drag_start(self, event): if self.component is not None: self._selected_hotspot = self._find_hotspot(event.x, event.y) super().drag_start(event) self.component._layout_needed = True if self.auto_raise: # Push the component to the top of its container's list self.component.container.raise_component(self.component) event.window.set_mouse_owner(self, event.net_transform()) event.handled = True return True # Private Interface ###################################################### def _find_hotspot(self, x, y): hotspot = [] if self.component is not None: c = self.component v_threshhold = min(self.threshhold, c.height / 2.0) if c.y <= y <= c.y + v_threshhold: hotspot.append("bottom") elif c.y2 + 1 - v_threshhold <= y <= c.y2 + 1: hotspot.append("top") elif y < c.y or y > c.y2 + 1: return "" h_threshhold = min(self.threshhold, c.width / 2.0) if c.x <= x <= c.x + h_threshhold: hotspot.append("left") elif c.x2 + 1 - h_threshhold <= x <= c.x2 + 1: hotspot.append("right") elif x < c.x or x > c.x2 + 1: return "" return " ".join(hotspot) # Traits Handlers ######################################################## def _hotspots_default(self): return set([ "top", "left", "right", "bottom", "top left", "top right", "bottom left", "bottom right", ]) def _minimum_bounds_default(self): return [self.threshhold * 2, self.threshhold * 2]
class Editor(HasPrivateTraits): """ Represents an editing control for an object trait in a Traits-based user interface. """ #: The UI (user interface) this editor is part of: ui = Instance("traitsui.ui.UI", clean_up=True) #: Full name of the object the editor is editing (e.g. #: 'object.link1.link2'): object_name = Str("object") #: The object this editor is editing (e.g. object.link1.link2): object = Instance(HasTraits, clean_up=True) #: The name of the trait this editor is editing (e.g. 'value'): name = ReadOnly() #: The context object the editor is editing (e.g. object): context_object = Property() #: The extended name of the object trait being edited. That is, #: 'object_name.name' minus the context object name at the beginning. For #: example: 'link1.link2.value': extended_name = Property() #: Original value of object.name (e.g. object.link1.link2.value): old_value = Any(clean_up=True) #: Text description of the object trait being edited: description = ReadOnly() #: The Item object used to create this editor: item = Instance(Item, (), clean_up=True) #: The GUI widget defined by this editor: control = Any(clean_up=True) #: The GUI label (if any) defined by this editor: label_control = Any(clean_up=True) #: Is the underlying GUI widget enabled? enabled = Bool(True) #: Is the underlying GUI widget visible? visible = Bool(True) #: Is the underlying GUI widget scrollable? scrollable = Bool(False) #: The EditorFactory used to create this editor: factory = Instance(EditorFactory, clean_up=True) #: Is the editor updating the object.name value? updating = Bool(False) #: Current value for object.name: value = Property() #: Current value of object trait as a string: str_value = Property() #: The trait the editor is editing (not its value, but the trait itself): value_trait = Property() #: The current editor invalid state status: invalid = Bool(False) # -- private trait definitions ------------------------------------------ #: A set to track values being updated to prevent infinite recursion. _no_trait_update = Set(Str) #: A list of all values synchronized to. _user_to = List(Tuple(Any, Str, Callable)) #: A list of all values synchronized from. _user_from = List(Tuple(Str, Callable)) # ------------------------------------------------------------------------ # Editor interface # ------------------------------------------------------------------------ # -- Abstract methods --------------------------------------------------- def init(self, parent): """ Create and initialize the underlying toolkit widget. This method must be overriden by subclasses. Implementations must ensure that the :attr:`control` trait is set to an appropriate toolkit object. Parameters ---------- parent : toolkit control The parent toolkit object of the editor's toolkit objects. """ raise NotImplementedError("This method must be overriden.") def update_editor(self): """ Updates the editor when the value changes externally to the editor. This should normally be overridden in a subclass. """ pass def error(self, excp): """ Handles an error that occurs while setting the object's trait value. This should normally be overridden in a subclass. Parameters ---------- excp : Exception The exception which occurred. """ pass def set_focus(self): """ Assigns focus to the editor's underlying toolkit widget. This method must be overriden by subclasses. """ raise NotImplementedError("This method must be overriden.") def string_value(self, value, format_func=None): """ Returns the text representation of a specified object trait value. This simply delegates to the factory's `string_value` method. Sub-classes may choose to override the default implementation. Parameters ---------- value : any The value being edited. format_func : callable or None A function that takes a value and returns a string. """ return self.factory.string_value(value, format_func) def restore_prefs(self, prefs): """ Restores saved user preference information for the editor. Editors with state may choose to override this. It will only be used if the editor has an `id` value. Parameters ---------- prefs : dict A dictionary of preference values. """ pass def save_prefs(self): """ Returns any user preference information for the editor. Editors with state may choose to override this. It will only be used if the editor has an `id` value. Returns ------- prefs : dict or None A dictionary of preference values, or None if no preferences to be saved. """ return None # -- Editor life-cycle methods ------------------------------------------ def prepare(self, parent): """ Finish setting up the editor. Parameters ---------- parent : toolkit control The parent toolkit object of the editor's toolkit objects. """ name = self.extended_name if name != "None": self.context_object.on_trait_change(self._update_editor, name, dispatch="ui") self.init(parent) self._sync_values() self.update_editor() def dispose(self): """ Disposes of the contents of an editor. This disconnects any synchronised values and resets references to other objects. Subclasses may chose to override this method to perform additional clean-up. """ if self.ui is None: return name = self.extended_name if name != "None": self.context_object.on_trait_change(self._update_editor, name, remove=True) for name, handler in self._user_from: self.on_trait_change(handler, name, remove=True) for object, name, handler in self._user_to: object.on_trait_change(handler, name, remove=True) # Break linkages to references we no longer need: for name in self.trait_names(clean_up=True): setattr(self, name, None) # -- Undo/redo methods -------------------------------------------------- def log_change(self, undo_factory, *undo_args): """ Logs a change made in the editor with undo/redo history. Parameters ---------- undo_factory : callable Callable that creates an undo item. Often self.get_undo_item. *undo_args Any arguments to pass to the undo factory. """ ui = self.ui # Create an undo history entry if we are maintaining a history: undoable = ui._undoable if undoable >= 0: history = ui.history if history is not None: item = undo_factory(*undo_args) if item is not None: if undoable == history.now: # Create a new undo transaction: history.add(item) else: # Extend the most recent undo transaction: history.extend(item) def get_undo_item(self, object, name, old_value, new_value): """ Creates an undo history entry. Can be overridden in a subclass for special value types. Parameters ---------- object : HasTraits instance The object being modified. name : str The name of the trait that is to be changed. old_value : any The original value of the trait. new_value : any The new value of the trait. """ return UndoItem(object=object, name=name, old_value=old_value, new_value=new_value) # -- Trait synchronization code ----------------------------------------- def sync_value( self, user_name, editor_name, mode="both", is_list=False, is_event=False, ): """ Synchronize an editor trait and a user object trait. Also sets the initial value of the editor trait from the user object trait (for modes 'from' and 'both'), and the initial value of the user object trait from the editor trait (for mode 'to'), as long as the relevant traits are not events. Parameters ---------- user_name : str The name of the trait to be used on the user object. If empty, no synchronization will be set up. editor_name : str The name of the relevant editor trait. mode : str, optional; one of 'to', 'from' or 'both' The direction of synchronization. 'from' means that trait changes in the user object should be propagated to the editor. 'to' means that trait changes in the editor should be propagated to the user object. 'both' means changes should be propagated in both directions. The default is 'both'. is_list : bool, optional If true, synchronization for item events will be set up in addition to the synchronization for the object itself. The default is False. is_event : bool, optional If true, this method won't attempt to initialize the user object or editor trait values. The default is False. """ if user_name == "": return key = "%s:%s" % (user_name, editor_name) parts = user_name.split(".") if len(parts) == 1: user_object = self.context_object xuser_name = user_name else: user_object = self.ui.context[parts[0]] xuser_name = ".".join(parts[1:]) user_name = parts[-1] if mode in {"from", "both"}: self._bind_from(key, user_object, xuser_name, editor_name, is_list) if not is_event: # initialize editor value from user value with self.raise_to_debug(): user_value = xgetattr(user_object, xuser_name) setattr(self, editor_name, user_value) if mode in {"to", "both"}: self._bind_to(key, user_object, xuser_name, editor_name, is_list) if mode == "to" and not is_event: # initialize user value from editor value with self.raise_to_debug(): editor_value = xgetattr(self, editor_name) xsetattr(user_object, xuser_name, editor_value) # -- Utility methods ----------------------------------------------------- def parse_extended_name(self, name): """ Extract the object, name and a getter from an extended name Parameters ---------- name : str The extended name to parse. Returns ------- object, name, getter : any, str, callable The object from the context, the (extended) name of the attributes holding the value, and a callable which gets the current value from the context. """ base_name, __, name = name.partition(".") if name: object = self.ui.context[base_name] else: name = base_name object = self.context_object return (object, name, partial(xgetattr, object, name)) # -- Utility context managers -------------------------------------------- @contextmanager def no_trait_update(self, name): """ Context manager that blocks updates from the named trait. """ if name in self._no_trait_update: yield return self._no_trait_update.add(name) try: yield finally: self._no_trait_update.remove(name) @contextmanager def raise_to_debug(self): """ Context manager that uses raise to debug to raise exceptions. """ try: yield except Exception: from traitsui.api import raise_to_debug raise_to_debug() @contextmanager def updating_value(self): """ Context manager to handle updating value. """ if self.updating: yield return self.updating = True try: yield finally: self.updating = False # ------------------------------------------------------------------------ # object interface # ------------------------------------------------------------------------ def __init__(self, parent, **traits): """ Initializes the editor object. """ super(HasPrivateTraits, self).__init__(**traits) try: self.old_value = getattr(self.object, self.name) except AttributeError: ctrait = self.object.base_trait(self.name) if ctrait.type == "event" or self.name == "spring": # Getting the attribute will fail for 'Event' traits: self.old_value = Undefined else: raise # Synchronize the application invalid state status with the editor's: self.sync_value(self.factory.invalid, "invalid", "from") # ------------------------------------------------------------------------ # private methods # ------------------------------------------------------------------------ def _update_editor(self, object, name, old_value, new_value): """ Performs updates when the object trait changes. This is designed to be used as a trait listener. """ # If background threads have modified the trait the editor is bound to, # their trait notifications are queued to the UI thread. It is possible # that by the time the UI thread dispatches these events, the UI the # editor is part of has already been closed. So we need to check if we # are still bound to a live UI, and if not, exit immediately: if self.ui is None: return # If the notification is for an object different than the one actually # being edited, it is due to editing an item of the form: # object.link1.link2.name, where one of the 'link' objects may have # been modified. In this case, we need to rebind the current object # being edited: if object is not self.object: self.object = self.ui.get_extended_value(self.object_name) # If the editor has gone away for some reason, disconnect and exit: if self.control is None: self.context_object.on_trait_change(self._update_editor, self.extended_name, remove=True) return # Log the change that was made (as long as the Item is not readonly # or it is not for an event): if (self.item.style != "readonly" and object.base_trait(name).type != "event"): # Indicate that the contents of the UI have been changed: self.ui.modified = True if self.updating: self.log_change(self.get_undo_item, object, name, old_value, new_value) # If the change was not caused by the editor itself: if not self.updating: # Update the editor control to reflect the current object state: self.update_editor() def _sync_values(self): """ Initialize and synchronize editor and factory traits Initializes and synchronizes (as needed) editor traits with the value of corresponding factory traits. The name of the factory trait and the editor trait must match and the factory trait needs to have ``sync_value`` metadata set. The strategy followed is: - for each factory trait with ``sync_value`` metadata: 1. if the value is a :class:`ContextValue` instance then call :meth:`sync_value` with the ``name`` from the context value. 2. if the trait has ``sync_name`` metadata, look at the referenced trait value and if it is a non-empty string then use this value as the name of the value in the context. 3. otherwise initialize the current value of the factory trait to the corresponding value of the editor. - synchronization mode in cases 1 and 2 is taken from the ``sync_value`` metadata of the editor trait first and then the ``sync_value`` metadata of the factory trait if that is empty. - if the value is a container type, then the `is_list` metadata is set to """ factory = self.factory for name, trait in factory.traits(sync_value=not_none).items(): value = getattr(factory, name) self_trait = self.trait(name) if self_trait.sync_value: mode = self_trait.sync_value else: mode = trait.sync_value if isinstance(value, ContextValue): self.sync_value( value.name, name, mode, bool(self_trait.is_list), self_trait.type == "event", ) elif (trait.sync_name is not None and getattr(factory, trait.sync_name, "") != ""): # Note: this is implemented as a stepping stone from things # like ``low_name`` and ``high_name`` to using context values. sync_name = getattr(factory, trait.sync_name) self.sync_value( sync_name, name, mode, bool(self_trait.is_list), self_trait.type == "event", ) elif value is not Undefined: setattr(self, name, value) def _bind_from(self, key, user_object, xuser_name, editor_name, is_list): """ Bind trait change handlers from a user object to the editor. Parameters ---------- key : str The key to use to guard against recursive updates. user_object : object The object in the TraitsUI context that is being bound. xuser_name: : str The extended name of the trait to be used on the user object. editor_name : str The name of the relevant editor trait. is_list : bool, optional If true, synchronization for item events will be set up in addition to the synchronization for the object itself. The default is False. """ def user_trait_modified(new): if key not in self._no_trait_update: with self.no_trait_update(key), self.raise_to_debug(): xsetattr(self, editor_name, new) user_object.on_trait_change(user_trait_modified, xuser_name) self._user_to.append((user_object, xuser_name, user_trait_modified)) if is_list: def user_list_modified(event): if (isinstance(event, TraitListEvent) and key not in self._no_trait_update): with self.no_trait_update(key), self.raise_to_debug(): n = event.index getattr(self, editor_name)[n:n + len(event.removed)] = event.added items = xuser_name + "_items" user_object.on_trait_change(user_list_modified, items) self._user_to.append((user_object, items, user_list_modified)) def _bind_to(self, key, user_object, xuser_name, editor_name, is_list): """ Bind trait change handlers from a user object to the editor. Parameters ---------- key : str The key to use to guard against recursive updates. user_object : object The object in the TraitsUI context that is being bound. xuser_name: : str The extended name of the trait to be used on the user object. editor_name : str The name of the relevant editor trait. is_list : bool, optional If true, synchronization for item events will be set up in addition to the synchronization for the object itself. The default is False. """ def editor_trait_modified(new): if key not in self._no_trait_update: with self.no_trait_update(key), self.raise_to_debug(): xsetattr(user_object, xuser_name, new) self.on_trait_change(editor_trait_modified, editor_name) self._user_from.append((editor_name, editor_trait_modified)) if is_list: def editor_list_modified(event): if key not in self._no_trait_update: with self.no_trait_update(key), self.raise_to_debug(): n = event.index value = xgetattr(user_object, xuser_name) value[n:n + len(event.removed)] = event.added self.on_trait_change(editor_list_modified, editor_name + "_items") self._user_from.append( (editor_name + "_items", editor_list_modified)) def __set_value(self, value): """ Set the value of the trait the editor is editing. This calls the appropriate setattr method on the handler to perform the actual change. """ with self.updating_value(): try: handler = self.ui.handler obj_name = self.object_name name = self.name method = (getattr(handler, "%s_%s_setattr" % (obj_name, name), None) or getattr(handler, "%s_setattr" % name, None) or getattr(handler, "setattr")) method(self.ui.info, self.object, name, value) except TraitError as excp: self.error(excp) raise # -- Traits property getters and setters -------------------------------- @cached_property def _get_context_object(self): """ Returns the context object the editor is using In some cases a proxy object is edited rather than an object directly in the context, in which case we return ``self.object``. """ object_name = self.object_name context_key = object_name.split(".", 1)[0] if (object_name != "") and (context_key in self.ui.context): return self.ui.context[context_key] # This handles the case of a 'ListItemProxy', which is not in the # ui.context, but is the editor 'object': return self.object @cached_property def _get_extended_name(self): """ Returns the extended trait name being edited. """ return ("%s.%s" % (self.object_name, self.name)).split(".", 1)[1] def _get_value_trait(self): """ Returns the trait the editor is editing (Property implementation). """ return self.object.trait(self.name) def _get_value(self): """ Returns the value of the trait the editor is editing. """ return getattr(self.object, self.name, Undefined) def _set_value(self, value): """ Set the value of the trait the editor is editing. Dispatches via the TraitsUI Undo/Redo mechanisms to make change reversible, if desired. """ if self.ui and self.name != "None": self.ui.do_undoable(self.__set_value, value) def _get_str_value(self): """ Returns the text representation of the object trait. """ return self.string_value(getattr(self.object, self.name, Undefined))
class Package(HasTraits): name = Str version = Str dependencies = Set(This) preinstalled = Dict(Str, This) buildInputs = Dict(Str, List(Str)) available = Dict(Str, This) # :: name -> Package frozen = Bool(False) def __eq__(self, other): return \ self.name == other.name and\ self.version == other.version def __str__(self): return '{}=={}'.format(self.name, self.version) @property def frozen_name(self): return str(self) def apply_version_transformations(self, transformations=None): tx = transformations or VERSION_TRANSFORMS logger.debug('%s applying version transformations %s', self, tx) if self.frozen: logger.debug('Using stored %s', self) return ver = self.version for old, new in tx.items(): ver = ver.replace(old, new) logger.debug('%s updating version to %s', self.name, ver) self.version = ver for dep in self.dependencies: dep.apply_version_transformations(transformations=tx) def find_requirements(self): logger.debug('Finding requirements for %s', self) if self.frozen: logger.debug('Using stored %s', self) return if is_cached(self): logger.debug('Using cached %s', self) return frozen = freeze([self.frozen_name], preinstalled=self.preinstalled.keys(), buildInputs=self.buildInputs.get(self.name)) for entry in frozen: if entry.name == self.name: continue pkg = self.available[entry.name] self.dependencies.add(pkg) logger.debug('\t%s', map(str, sorted(list(self.dependencies)))) def prune_dependencies(self): logger.debug('Prunning dependencies for %s', self) if self.frozen: logger.debug('Using stored %s', self) return dep_names = set([dep.name for dep in self.dependencies]) # :: {str} to_prune = set() # :: {str} logger.debug('%s children: %s', self.name, ' '.join(dep_names)) for child in self.dependencies: if not is_cached(child): child.find_requirements() logger.debug( '%s grandchildren: %s', self.name, ' '.join(map(lambda p: p.name, child.dependencies))) cache(child) else: logger.debug('Using cached %s', child) for grandchild in child.dependencies: if grandchild.name in dep_names: to_prune.add(grandchild.name) logger.debug('Prunning for %s: %s', self, ' '.join(to_prune)) pruned = filter(lambda dep: dep.name not in to_prune, self.dependencies) self.dependencies = set(pruned) def freeze(self, store): self.frozen = True store.set(self.name, self.version, self)
class StyleSheet(HasStrictTraits): """ An object representing a style sheet. The style sheet is constructed with a sequence of 'style' objects which are then parsed into an internal dictionary of selectors. The value for a style property which matches style criteria can be retrieved through the 'get_property' method. Attributes ---------- updated : Event An event which is fired when the style sheet has changed. The payload of the event will be a set of property names that have been updated. This event is not fired when the sheet is first instantiated. Methods ------- update(*styles) Update the style sheet with the given style objects. This will trigger an 'updated' event. replace(*styles) Replace the style sheet with the give style objects. This will trigger an 'updated' event. """ # Maps the style selector string to the created Selector object. _selectors = Dict(Str, Instance(Selector)) # Maps a property name to the set of Selectors # which contain a property with that name. _property_selectors = Dict(Str, Set(Instance(Selector))) # A counter that is used _counter = Instance(itertools.count, ()) updated = Event def __init__(self, *styles): """ Construct a style sheet object. Parameters ---------- styles : Sequence of 'style' objects. The style objects with which to populate the sheet. """ super(StyleSheet, self).__init__() self._selectors = {} self._property_selectors = {} self._parse_styles(*styles) def _parse_styles(self, *styles): """ A private method which parses the styles in the appropriate selector objects. """ selector_map = self._selectors counter = self._counter updated_selectors = set() for sty in styles: selector_strings = sty.selectors properties = sty.properties for selector_str in selector_strings: match = TYPE_SELECTOR.match(selector_str) if match: match_str = match.group(1) if match_str in selector_map: selector = selector_map[match_str] else: selector = TypeSelector(node_type=match_str) selector_map[match_str] = selector selector.order = counter.next() selector.properties.update(properties) updated_selectors.add(selector) continue match = CLASS_SELECTOR.match(selector_str) if match: match_str = match.group(1) if match_str in selector_map: selector = selector_map[match_str] else: node_classes = set(filter(None, match_str.split('.'))) selector = ClassSelector(node_classes=node_classes) selector_map[match_str] = selector selector.order = counter.next() selector.properties.update(properties) updated_selectors.add(selector) continue match = QUAL_SELECTOR.match(selector_str) if match: match_str = selector_str if match_str in selector_map: selector = selector_map[match_str] else: node_type, rest = match_str.split('.', 1) node_classes = set(rest.split('.')) selector = QualSelector(node_type=node_type, node_classes=node_classes) selector_map[match_str] = selector selector.order = counter.next() selector.properties.update(properties) updated_selectors.add(selector) continue match = ID_SELECTOR.match(selector_str) if match: match_str = match.group(1) if match_str in selector_map: selector = selector_map[match_str] else: selector = IDSelector(node_id=match_str[1:]) selector_map[match_str] = selector selector.order = counter.next() selector.properties.update(properties) updated_selectors.add(selector) continue match = DEFAULT_SELECTOR.match(selector_str) if match: match_str = match.group(1) if match_str in selector_map: selector = selector_map[match_str] else: selector = Selector() selector_map[match_str] = selector selector.order = counter.next() selector.properties.update(properties) updated_selectors.add(selector) continue property_selectors = self._property_selectors updated_properties = set() for selector in updated_selectors: for key in selector.properties: property_selectors.setdefault(key, set()).add(selector) updated_properties.add(key) return updated_properties def update(self, *styles): """ Update the style sheet with the given style objects. This will trigger a 'sheet_updated' event. Parameters ---------- styles : Sequence of 'style' objects. The style objects with which to update the sheet. """ self.updated = self._parse_styles(*styles) def replace(self, *styles): """ Replace the style sheet with the give style objects. Parameters ---------- styles : Sequence of 'style' objects. The style objects with which to populate the sheet. """ self._selectors = {} self._property_selectors = {} self._counter = itertools.count() self.updated = self._parse_styles(*styles) def get_property(self, tag, node_data): """ Returns the style property for the given tag. This method is used to query the style sheet for the value of a property for a particular node as described by the given style node data object. Arguments --------- node_data : IStyleNodeData An object for the node being styled which implements the IStyleNodeData interface. Returns ------- result : style property or NO_STYLE Returns the style property for the most specific match of the sheet, or NO_STYLE if no match is found. """ selectors = self._property_selectors.get(tag) if not selectors: return NO_STYLE matches = (selector.match(node_data) for selector in selectors) specs = (spec for match, spec in matches if match) # If there are no matches, max will bail on the empty sequence. try: res = StyleValue(max(specs)[2][tag]) except ValueError: res = NO_STYLE return res def get_tags(self): """ Returns an iterable of all the tags in the style_sheet. """ return self._property_selectors.iterkeys() def has_tag(self, tag): """ Returns True or False depending on whether the tag is in the style sheet. """ return tag in self._property_selectors
class ValueDragTool(DragTool): """ Abstract tool for modifying a value as the mouse is dragged The tool allows the use of an x_mapper and y_mapper to map between screen coordinates and more abstract data coordinates. These mappers must be objects with a map_data() method that maps a component-space coordinate to a data-space coordinate. Chaco mappers satisfy the required API, and the tool will look for 'x_mapper' and 'y_mapper' attributes on the component to use as the defaults, facilitating interoperability with Chaco plots. Failing that, a simple identity mapper is provided which does nothing. Coordinates are given relative to the component. Subclasses of this tool need to supply get_value() and set_delta() methods. The get_value method returns the current underlying value, while the set_delta method takes the current mapped x and y deltas from the original position, and sets the underlying value appropriately. The object stores the original value at the start of the operation as the original_value attribute. """ #: set of modifier keys that must be down to invoke the tool modifier_keys = Set(Enum(*keys)) #: mapper that maps from horizontal screen coordinate to data coordinate x_mapper = Any #: mapper that maps from vertical screen coordinate to data coordinate y_mapper = Any #: start point of the drag in component coordinates original_screen_point = Tuple(Float, Float) #: start point of the drag in data coordinates original_data_point = Tuple(Any, Any) #: initial underlying value original_value = Any #: new_value event for inspector overlay new_value = Event(Dict) #: visibility for inspector overlay visible = Bool(False) def get_value(self): """ Return the current value that is being modified """ pass def set_delta(self, value, delta_x, delta_y): """ Set the value that is being modified This function should modify the underlying value based on the provided delta_x and delta_y in data coordinates. These deltas are total displacement from the original location, not incremental. The value parameter is the original value at the point where the drag started. """ pass # Drag tool API def drag_start(self, event): self.original_screen_point = (event.x, event.y) data_x = self.x_mapper.map_data(event.x) data_y = self.y_mapper.map_data(event.y) self.original_data_point = (data_x, data_y) self.original_value = self.get_value() self.visible = True return True def dragging(self, event): position = event.current_pointer_position() delta_x = (self.x_mapper.map_data(position[0]) - self.original_data_point[0]) delta_y = (self.y_mapper.map_data(position[1]) - self.original_data_point[1]) self.set_delta(self.original_value, delta_x, delta_y) return True def drag_end(self, event): event.window.set_pointer("arrow") self.visible = False return True def _drag_button_down(self, event): # override button down to handle modifier keys correctly if not event.handled and self._drag_state == "nondrag": key_states = dict((key, key in self.modifier_keys) for key in keys) if (not all( getattr(event, key + "_down") == state for key, state in key_states.items())): return False self.mouse_down_position = (event.x, event.y) if not self.is_draggable(*self.mouse_down_position): self._mouse_down_recieved = False return False self._mouse_down_received = True return True return False # traits default handlers def _x_mapper_default(self): # if the component has an x_mapper, try to use it by default return getattr(self.component, "x_mapper", identity_mapper) def _y_mapper_default(self): # if the component has an x_mapper, try to use it by default return getattr(self.component, "y_mapper", identity_mapper)
class CustomEditor(Editor): """ Custom Traits UI date editor that wraps QCalendarWidget. """ #: Style used for when a date is unselected. #: Mapping from datetime.date to CellFormat _unselected_styles = Dict(Date, Instance(CellFormat)) #: Selected dates (used when multi_select is true) _selected = Set(Date) def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ self.control = QtGui.QCalendarWidget() if not self.factory.allow_future: self.control.setMaximumDate(QtCore.QDate.currentDate()) self.control.clicked.connect(self.update_object) def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ value = self.value if value: if not self.factory.multi_select: q_date = QtCore.QDate(value.year, value.month, value.day) self.control.setSelectedDate(q_date) else: self.apply_unselected_style_to_all() for date in value: self.apply_style(self.factory.selected_style, date) self._selected = set(value) def update_object(self, q_date): """ Handles the user entering input data in the edit control. """ value = datetime.date(q_date.year(), q_date.month(), q_date.day()) if self.factory.multi_select: if value in self.value: self.unselect_date(value) else: self.select_date(value) self.value = sorted(self._selected) else: self.value = value def unselect_date(self, date): self._selected.remove(date) self.apply_unselected_style(date) def select_date(self, date): self._selected.add(date) self.apply_style(self.factory.selected_style, date) def set_unselected_style(self, style, date): """ Set the style used for a date when it is not selected.""" self._unselected_styles[date] = style if self.factory.multi_select: if date not in self.value: self.apply_style(style, date) else: self.apply_style(style, date) def apply_style(self, style, date): """ Apply a given style to a given date.""" qdt = QtCore.QDate(date) textformat = self.control.dateTextFormat(qdt) _apply_cellformat(style, textformat) self.control.setDateTextFormat(qdt, textformat) def apply_unselected_style(self, date): """ Apply the style for when a date is unselected.""" # Resets the text format on the given dates textformat = QtGui.QTextCharFormat() if date in self._unselected_styles: _apply_cellformat(self._unselected_styles[date], textformat) qdt = QtCore.QDate(date) self.control.setDateTextFormat(qdt, textformat) def apply_unselected_style_to_all(self): """ Make all the selected dates appear unselected. """ for date in self._selected: self.apply_unselected_style(date)
class IOController(HasStrictTraits): ### Current Sensor Values ################################################ acc_x = Float(plot_data=True, comparison_mode=NO_COMPARE) acc_y = Float(plot_data=True, comparison_mode=NO_COMPARE) acc_z = Float(plot_data=True, comparison_mode=NO_COMPARE) switch = Float(plot_data=True, comparison_mode=NO_COMPARE) distance = Float(plot_data=True, comparison_mode=NO_COMPARE) potentiometer = Float(plot_data=True, comparison_mode=NO_COMPARE) ### Plots ################################################################ logo_plot = Instance(Figure) acc_x_plot = Instance(Plot) acc_y_plot = Instance(Plot) acc_z_plot = Instance(Plot) switch_plot = Instance(Plot) distance_plot = Instance(Plot) pot_plot = Instance(Plot) link_plot = Instance(Component) plot_data = Instance(ArrayPlotData) line = Any() ax = Any() ### Outputs ############################################################## led = Int(output=True) servo = Int(output=True) motor = Int(output=True) ### IOController Interface ############################################### added_links = List() removed_links = List() outputs = Dict() ### Private Traits ####################################################### _current_links = Set() ### Trait Defaults ####################################################### def _logo_plot_default(self): fig = Figure() ax = Axes3D(fig) line, = ax.plot((1, 2), (1, 2), (1, 2)) self.line = line self.ax = ax self.ax.set_xlim(0, 1, auto=False) self.ax.set_ylim(0, 1, auto=False) self.ax.set_zlim(0, 1, auto=False) return fig def _acc_x_plot_default(self): plot = Plot(self.plot_data) plot.plot(('acc_x', )) plot.padding = (0, 0, 0, 0) plot.value_mapper.range.low_setting = 0 plot.value_mapper.range.high_setting = 1 return plot def _acc_y_plot_default(self): plot = Plot(self.plot_data) plot.plot(('acc_y', )) plot.padding = (0, 0, 0, 0) plot.value_mapper.range.low_setting = 0 plot.value_mapper.range.high_setting = 1 return plot def _acc_z_plot_default(self): plot = Plot(self.plot_data) plot.plot(('acc_z', )) plot.padding = (0, 0, 0, 0) plot.value_mapper.range.low_setting = 0 plot.value_mapper.range.high_setting = 1 return plot def _switch_plot_default(self): plot = Plot(self.plot_data) plot.plot(('switch', )) plot.padding = (0, 0, 0, 0) plot.value_mapper.range.low_setting = 0 plot.value_mapper.range.high_setting = 1 return plot def _distance_plot_default(self): plot = Plot(self.plot_data) plot.plot(('distance', )) plot.padding = (0, 0, 0, 0) plot.value_mapper.range.low_setting = 0 plot.value_mapper.range.high_setting = 1 return plot def _pot_plot_default(self): plot = Plot(self.plot_data) plot.plot(('potentiometer', )) plot.padding = (0, 0, 0, 0) plot.value_mapper.range.low_setting = 0 plot.value_mapper.range.high_setting = 1 return plot def _link_plot_default(self): return LinksComponent() def _plot_data_default(self): plot_data = ArrayPlotData() plot_data.set_data('distance', np.zeros(50)) plot_data.set_data('potentiometer', np.zeros(50)) plot_data.set_data('switch', np.zeros(50)) plot_data.set_data('acc_x', np.zeros(50)) plot_data.set_data('acc_y', np.zeros(50)) plot_data.set_data('acc_z', np.zeros(50)) return plot_data def clicked(self, win): import ipdb ipdb.set_trace() # XXX BREAKPOINT ### Trait Change Handlers ################################################ @on_trait_change('acc_x, acc_y, acc_z') def _update_3d_plot(self): if self.line and self.ax and self.ax.figure.canvas: x, y, z = self.acc_x, self.acc_y, self.acc_z #self.line.set_data(np.array([[0, 0, 0], [x, y, z]]).T) data = np.array([[.5, .5, .5], [x, y, z]]).T self.line.set_data(data[0:2, :]) self.line.set_3d_properties(data[2, :]) self.ax.figure.canvas.draw() #print x, y, z #self.ax.clear() #self.ax.plot((0, x), (0, y), (0, z)) #self.ax.set_xlim(0, 1, auto=False) #self.ax.set_ylim(0, 1, auto=False) #self.ax.set_zlim(0, 1, auto=False) #self.ax.figure.canvas.draw() @on_trait_change('+plot_data') def _push_to_plot_data(self, name, new): # XXX This is causing NSConcreteMapTable to leak ary = self.plot_data[name] if ary is not None: ary = np.append(ary, new) ary = ary[-50:] self.plot_data.set_data(name, ary) @on_trait_change('+output') def _push_to_server(self, name, new): self.outputs[name] = new print self.outputs @on_trait_change('link_plot.links[]') def _links_changed(self, new): new = set(new) old = self._current_links added = new - old added_links = [] for i, out in added: added_links.append((INPUT_MAP[i], OUTPUT_MAP[out])) removed = old - new removed_links = [] for i, out in removed: removed_links.append((INPUT_MAP[i], OUTPUT_MAP[out])) self._current_links = new self.added_links.extend(added_links) self.removed_links.extend(removed_links) print added, removed
class Test(HasTraits): var = Set(trait=Int())
class Foo(HasTraits): values = Set(items=False)
class TestSet(HasTraits): letters = Set(Str())
class Foo(HasTraits): values = Set()
class OfType(AbstractQuery): """ Gives all objects of given type that are found in System Usage & example:: OfType(type, **kwargs) OfType(AbstractActuator, exclude=['actuator1', 'actuator2']) # returns all actuators in system, except those named 'actuator1' and 'actuator2'. :param list exclude: list of instances to be excluded from the returned list. """ system_objects_changed = Event triggers = Property( trait=Set(trait=StatusObject), depends_on= 'on_setup_callable, _kwargs_items, _args_items, system_objects_changed' ) targets = Property( trait=Set(trait=StatusObject), depends_on= 'on_setup_callable, _kwargs, _kwargs_items, _args, _args_items, ' 'system_objects_changed') setup_complete = CBool(transient=True) @on_trait_change('system') def set_setup_complete(self, name, new): if name == 'system': self.system.on_trait_change(self.set_setup_complete, 'post_init_trigger') if name == 'post_init_trigger': self.setup_complete = True @on_trait_change('system.objects, system.objects_items, setup_complete') def trigger_system_objects_changed(self): if self.setup_complete: self.system_objects_changed = 1 def _both(self): if not self.system: return set() exclude = set(self._kwargs.get('exclude', [])) # exclude must be list type return { i for i in self.system.objects if isinstance(i, tuple(self.objects)) } - exclude @cached_property def _get_triggers(self): if self._kwargs.get('type', 'both') in ['triggers', 'both']: return self._both() return set() @cached_property def _get_targets(self): if self._kwargs.get('type', 'both') in ['targets', 'both']: return self._both() return set() def call(self, caller=None, **kwargs): return self.targets def give_str(self): args = [ReprObject(i.__name__) for i in self.objects] return self._give_str(args, self._kwargs) def give_str_indented(self, tags=False): args = [ReprObject(i.__name__) for i in self.objects] return self._give_str_indented(args, self._kwargs, tags)
class AbstractCallable(SystemObject, CompareMixin): """ A base class for subclassing Callables that are used in Program conditions and action attributes. Callables are configured by giving them arguments and keyword arguments.They must always define :meth:`.call` method which defines their functionality. """ #: Arguments given for callable are stored here _args = CList #: Keyword arguments given for callable are stored here _kwargs = Dict #: Lock must be used when accessing :attr:`.state` _lock = Instance(Lock, transient=True) #: Property that gives set of all *triggers* of this callable and it's children callables. #: Triggers are all those StatusObjects that alter the status (return value of :meth:`.call`) of #: Callable. triggers = Property( trait=Set(trait=AbstractStatusObject), depends_on= '_args, _args_items, _args.triggers, _kwargs, _kwargs_items, _kwargs.triggers' ) #: Property that gives set of all *targets* of this callable and it's children callables. Targets are #: all those StatusObjects of which status the callable might alter in :meth:`.call`. targets = Property( trait=Set(trait=AbstractStatusObject), depends_on= '_args, _args_items, _args.targets, _kwargs, _kwargs_items, _kwargs.targets' ) # Status property not yet used anywhere. In the future, Program.active could be changed to Property # which uses this status. # Remark: isn't it used in web? #: Read-only status property of the callable. Usefull only when callable is used as a condition. #: This automatically depends on all the StatusObjects below the Callable tree. status = Property(depends_on='_args.status, _kwargs.status') #: State dictionary that is used by :meth:`.call` and :meth:`.cancel` if some state variables are needed to be saved #: Remember to clean data in subclasses when it is no longer needed. state = Dict(transient=True) def get_state(self, caller): """ Get per-program state. """ if caller in self.state: return self.state[caller] else: rv = self.state[caller] = DictObject() return rv def del_state(self, caller): """ Delete per-program state. """ if caller in self.state: del self.state[caller] @cached_property def _get_status(self): if not self.system: # Raising exception prevents invalid value from being cached when system is in pre-mature state raise SystemNotReady( 'System not ready yet -- this is normal when loading dump.') return self.call(None) #: Event that can be used to execute code right after callable setup. See :class:`.OfType`. #: Something that needs to be done manually this way, because Traits does not allow #: defining the order of subscribed function calls. on_setup_callable = Event def setup_callables(self): super().setup_callables() self.setup_callable_system(self.system, init=True) @on_trait_change('_args, _args_items', post_init=True) def objects_changed(self, name, old, new): if not self.system: return for o in new.added: if isinstance(o, AbstractCallable): o.setup_callable_system(self.system) @on_trait_change('_kwargs, _kwargs_items', post_init=True) def kwargs_changed(self, name, old, new): if not self.system: return for o in list(new.added.values()): if isinstance(o, AbstractCallable): o.setup_callable_system(self.system) @cached_property def _get_triggers(self): if not self.system: # Raising exception prevents invalid value from being cached when system is in pre-mature state raise SystemNotReady( 'System not ready yet -- this is normal when loading dump.') triggers = self.collect('triggers') self.logger.debug('Collected triggers for %s (%s): %s', self, id(self), triggers) return triggers @cached_property def _get_targets(self): if not self.system: # Raising exception prevents invalid value from being cached when system is in pre-mature state raise SystemNotReady( 'System not ready yet -- this is normal when loading dump.') targets = self.collect('targets') self.logger.debug('Collected targets for %s (%s): %s', self, id(self), targets) return targets def __getitem__(self, item): return self._args[item] def __setitem__(self, item, value): self._args[item] = value def __init__(self, *args, **kwargs): self._lock = Lock("Lock for callable " + self.__class__.__name__) self._kwargs = kwargs super().__init__() super(SystemObject, self).__init__() if not self.traits_inited(): self.logger.error('Traits not inited!!!') if args: self._args = args def __setstate__(self, state, trait_change_notify=True): self._lock = Lock("Lock for callable ") self._passed_arguments = None, state.copy() self.logger = logging.getLogger('automate.%s' % self.__class__.__name__) state.pop('name', '') super(SystemObject, self).__setstate__(state, trait_change_notify) def call_eval(self, value, caller, return_value=True, **kwargs): """ Value might be either name registered in System namespace, or object, either StatusObject or Callable. If Callable, evaluate :meth:`.call` method. If StatusObject, return status. """ value = self.name_to_system_object(value) if return_value and isinstance(value, AbstractStatusObject): return value.status if hasattr(value, 'call'): return self.call_eval(value.call(caller, **kwargs), caller, return_value, **kwargs) else: return value def _fix_list(self, lst): if isinstance(lst, dict): lst2 = list(lst.items()) elif isinstance(lst, list): lst2 = enumerate(lst) else: raise RuntimeError('Error in _fix_list, type %s', type(lst)) for idx, obj in lst2: if isinstance(obj, str): lst[idx] = self.name_to_system_object(obj) if isinstance(obj, list): self._fix_list(obj) def setup_callable_system(self, system, init=False): """ This function basically sets up :attr:`.system`, if it is not yet set up. After that, other Callable initialization actions are performed. :param init: value ``True`` is given when running this at the initialization phase. Then system attribute is set already, but callable needs to be initialized otherwise. """ if not self.system or init: self.system = system self._fix_list(self._args) self._fix_list(self._kwargs) for i in self.children: if isinstance(i, AbstractCallable): i.setup_callable_system(system, init=init) elif isinstance(i, SystemObject): i.system = system self.on_setup_callable = 1 self.logger.debug('setup_callable_system for %s (%s) ready.', self, id(self)) def call(self, *args, **kwargs): """ The basic functionality of the Callable is implemented in this function. Needs to be defined in derived subclasses. If callable is used as a Program condition, this must return the value of the condition (see for example conditions :class:`.And`, :class:`.Sum` etc.), otherwise return value is optional. """ raise NotImplementedError @property def objects(self): """ Shortcut to :attr:`._args`. """ return self._args @property def obj(self): """ Shortcut property to the first stored object. """ try: return self._args[0] except IndexError: return None @property def value(self): """ Shortcut property to the second stored object. """ try: return self._args[1] except IndexError: return None def name_to_system_object(self, value): """ Return object for given name registered in System namespace. """ if not self.system: raise SystemNotReady if isinstance(value, (str, Object)): rv = self.system.name_to_system_object(value) return rv if rv else value else: return value def collect(self, target): """Recursively collect all potential triggers/targets in this node and its children. Define targets and triggers of this particular callable in :meth:`_give_triggers` and :meth:`_give_targets`. :param str target: valid values: ``'targets'`` and ``'triggers'`` """ statusobjects = set() callables = set() objs_from_this_obj = getattr(self, '_give_%s' % target)() if not is_iterable(objs_from_this_obj): objs_from_this_obj = [objs_from_this_obj] if is_iterable(objs_from_this_obj): for i in (self.name_to_system_object(j) for j in objs_from_this_obj): if isinstance(i, AbstractStatusObject): statusobjects.add(i) elif isinstance(i, AbstractCallable): callables.add(i) for i in (self.name_to_system_object(j) for j in deep_iterate(callables)): if isinstance(i, AbstractCallable): statusobjects.update(getattr(i, target)) return statusobjects def collect_triggers(self): return self.collect('triggers') def collect_targets(self): return self.collect('targets') @property def children(self): """ A property giving a generator that goes through all the children of this Callable (not recursive) """ return deep_iterate(self._args + list(self._kwargs.values())) def _give_triggers(self): """Give all triggers of this object (non-recursive)""" return self.children def _give_targets(self): """Give all targets of this object (non-recursive)""" return self.children def cancel(self, caller): """ Recursively cancel all threaded background processes of this Callable. This is called automatically for actions if program deactivates. """ for o in {i for i in self.children if isinstance(i, AbstractCallable)}: o.cancel(caller) def __repr__(self): return self.give_str() def __str__(self): return self.give_str() def _give_str(self, args, kwargs): if self in self.system.namespace.reverse: return repr(self.name) kwstr = u', '.join(k + u'=' + repr(v) for k, v in list(kwargs.items())) if kwstr and args: kwstr = ', ' + kwstr return str(self.__class__.__name__) + u"(" + u", ".join( [repr(i) for i in args]) + kwstr + u")" def give_str(self): """ Give string representation of the callable. """ args = self._args[:] kwargs = self._kwargs return self._give_str(args, kwargs) @staticmethod def strip_color_tags(_str): return re.sub('__\w*__', "", _str) def _give_str_indented(self, args, kwargs, tags): if self in self.system.namespace.reverse: rv = repr(self.name) def indent(o_str): n_strs = [] for o in o_str.split('\n'): n_strs.append(u' ' + o) return '\n'.join(n_strs) def indented_str(obj, no_repr=False, no_color=False): if hasattr(obj, 'give_str_indented' ) and not obj in self.system.namespace.reverse: rv = obj.give_str_indented(tags) else: rv = str(obj if no_repr else repr(obj)) if not no_color: rv = ('__ACT__' if getattr(obj, 'status', obj) else '__INACT__') + rv return indent(rv) def in_one_line(obj): rv = repr(obj) if not isinstance(obj, AbstractCallable): rv = ('__ACT__' if getattr(obj, 'status', obj) else '__INACT__') + rv return rv kwstrs = [k + u'=' + in_one_line(v) for k, v in list(kwargs.items())] argstr = u"(\n" + indent(u", \n".join( [indented_str(i) for i in args] + [indented_str(i, no_repr=True, no_color=True) for i in kwstrs]) + u"\n)") if len(self.strip_color_tags(argstr)) < 35: argstr = u"(" + u", ".join([in_one_line(i) for i in args] + kwstrs) + u")" rv = str(self.__class__.__name__) + argstr if tags: rv = ('__ACT__' if self.status else '__INACT__') + rv return rv def give_str_indented(self, tags=False): """ Give indented string representation of the callable. This is used in :ref:`automate-webui`. """ args = self._args[:] kwargs = self._kwargs rv = self._give_str_indented(args, kwargs, tags) if not tags: rv = self.strip_color_tags(rv) return rv def __eq__(self, other): if isinstance(other, self.__class__): return (self._args, self._kwargs) == (other._args, other._kwargs) return False def __hash__(self): return id(self)
class ClassWithSetOfInstance(HasTraits): instances = Set(Instance(SingleValue)) instances_compat = Set(Instance(SingleValue))
class HasEnumInList(HasTraits): #: Valid digits digits = Set(Int) #: Sequence of those digits digit_sequence = List(Enum(values="digits"))