class Bool(Variable): """A variable wrapper for a boolean variable. """ def __init__(self, default_value=False, iotype=None, desc=None, **metadata): # Put iotype in the metadata dictionary if iotype is not None: metadata['iotype'] = iotype # Put desc in the metadata dictionary if desc is not None: metadata['desc'] = desc self._validator = Enthought_Bool(value=default_value, **metadata) super(Bool, self).__init__(default_value=default_value, **metadata) def validate(self, obj, name, value): """ Use the Enthought trait's validate. """ return self._validator.validate(obj, name, value) def create_editor(self): """ User the one in the Enthought trait. """ return self._validator.create_editor() def get_attribute(self, name, value, trait, meta): """Return the attribute dictionary for this variable. This dict is used by the GUI to populate the edit UI. Bools need to turn their value into a string for compatibility. name: str Name of variable value: object The value of the variable trait: CTrait The variable's trait meta: dict Dictionary of metadata for this variable """ attr = {} attr['name'] = name attr['type'] = type(value).__name__ attr['value'] = str(value) for field in meta: if field not in gui_excludes: attr[field] = meta[field] return attr, None
def __init__(self, default_value=False, iotype=None, desc=None, **metadata): # Put iotype in the metadata dictionary if iotype is not None: metadata['iotype'] = iotype # Put desc in the metadata dictionary if desc is not None: metadata['desc'] = desc self._validator = Enthought_Bool(value=default_value, **metadata) super(Bool, self).__init__(default_value=default_value, **metadata)
def __init__(self, default_value=False, iotype=None, desc=None, **metadata): # Put iotype in the metadata dictionary if iotype is not None: metadata['iotype'] = iotype # Put desc in the metadata dictionary if desc is not None: metadata['desc'] = desc assumed_default = (default_value is None) if 'assumed_default' in metadata: del metadata['assumed_default'] self._validator = Enthought_Bool(value=default_value, **metadata) super(Bool, self).__init__(default_value=default_value, assumed_default=assumed_default, **metadata)
class TFunPWLInteractive(MFnLineArray, BMCSLeafNode, Vis2D): '''Interactive time function. ''' node_name = 'time function' t_values = List(Float, [0]) f_values = List(Float, [0]) def reset(self): self.f_values = [0] self.t_values = [0] self.f_value = self.f_min n_f_values = Int(10, input=True, auto_set=False, enter_set=True) f_min = Float(0.0, input=True, auto_set=False, enter_set=True, label='F minimum') f_max = Float(1.0, input=True, auto_set=False, enter_set=True, label='F maximum') t_ref = Float(1.0, auto_set=False, enter_set=True, label='Initial time range') f_value = Range(low='f_min', high='f_max', value=0, input=True, auto_set=False, enter_set=True) enable_slider = Bool(True, disable_on_run=True) run_eagerly = Bool(True, label='Run eagerly') t_snap = Float(0.1, label='Time step to snap to', auto_set=False, enter_set=True) def __init__(self, *arg, **kw): super(TFunPWLInteractive, self).__init__(*arg, **kw) self.xdata = np.array(self.t_values) self.ydata = np.array(self.f_values) d_t = Property(depends_on='t_ref,n_f_values') @cached_property def _get_d_t(self): return self.t_ref / self.n_f_values def _update_xy_arrays(self): delta_f = self.f_value - self.f_values[-1] self.f_values.append(self.f_value) rel_step = delta_f / (self.f_max - self.f_min) delta_t = rel_step * self.t_ref t_value = np.fabs(delta_t) + self.t_values[-1] n_steps = int(t_value / self.t_snap) + 1 t_value = n_steps * self.t_snap self.t_values.append(t_value) self.xdata = np.array(self.t_values) self.ydata = np.array(self.f_values) self.replot() def _f_value_changed(self): self._update_xy_arrays() t_value = self.t_values[-1] f_value = self.f_values[-1] if self.ui: self.ui.model.set_tmax(t_value) if self.run_eagerly: print('LS-run', t_value, f_value) self.ui.run() def get_ty_data(self, vot): return self.t_values, self.f_values viz2d_classes = { 'time_function': TFViz2D, } tree_view = View( VGroup( VSplit( VGroup( VGroup( Include('actions'), ), Tabbed( VGroup( VGroup( UItem('f_value', full_size=True, resizable=True, enabled_when='enable_slider' ), ), VGroup( Item('f_max', full_size=True, resizable=True), Item('f_min', full_size=True), Item('n_f_values', full_size=True), Item('t_snap', tooltip='Snap value to round off' 'the value to', full_size=True), ), spring, label='Steering', ), VGroup( Item('run_eagerly', full_size=True, resizable=True, tooltip='True - run calculation immediately' 'after moving the value slider; \nFalse - user must' 'start calculation by clicking Run button'), spring, label='Mode', ), ), ), UItem('figure', editor=MPLFigureEditor(), height=300, resizable=True, springy=True), ), ) ) traits_view = tree_view
class MplPlot(BasePlot, HasTraitsGroup): figure = Instance(Figure, ()) _draw_pending = Bool(False) scale = Enum('linear', 'log', 'sqrt')('linear') scale_values = [ 'linear', 'log', 'sqrt' ] # There's probably a way to exract this from the Enum trait but I don't know how azimuth = Range(-90, 90, -70) elevation = Range(0, 90, 30) quality = Range(1, MAX_QUALITY, 1) flip_order = Bool(False) x_lower = Float(0.0) x_upper = Float x_label = Str('Angle (2$\Theta$)') y_label = Str('Dataset') z_lower = Float(0.0) z_upper = Float z_label = Str z_labels = {} # A dictionary to hold edited labels for each scaling type group = VGroup( HGroup( VGroup( Item('azimuth', editor=DefaultOverride(mode='slider', auto_set=False, enter_set=True)), Item('elevation', editor=DefaultOverride(mode='slider', auto_set=False, enter_set=True)), Item('quality'), Item('flip_order'), ), VGroup( HGroup( Item('x_label', editor=DefaultOverride(auto_set=False, enter_set=True)), Item('x_lower', editor=DefaultOverride(auto_set=False, enter_set=True)), Item('x_upper', editor=DefaultOverride(auto_set=False, enter_set=True)), ), HGroup(Item('y_label'), ), HGroup( Item('z_label', editor=DefaultOverride(auto_set=False, enter_set=True)), Item('z_lower', editor=DefaultOverride(auto_set=False, enter_set=True)), Item('z_upper', editor=DefaultOverride(auto_set=False, enter_set=True)), ), ), ), UItem('figure', editor=MPLFigureEditor()), ) def __init__(self, callback_obj=None, *args, **kws): super(MplPlot, self).__init__(*args, **kws) self.figure = plt.figure() self.figure.subplots_adjust(bottom=0.05, left=0, top=1, right=0.95) self.ax = None for s in self.scale_values: self.z_labels[s] = 'Intensity - ' + get_value_scale_label(s, mpl=True) # This must be a weak reference, otherwise the entire app will # hang on exit. from weakref import proxy if callback_obj: self._callback_object = proxy(callback_obj) else: self._callback_object = lambda *args, **kw: None def close(self): del self._callback_object plt.close() def __del__(self): plt.close() @on_trait_change('azimuth, elevation') def _perspective_changed(self): if self.ax: self.ax.view_init(azim=self.azimuth, elev=self.elevation) self.redraw() def _quality_changed(self): self.redraw(replot=True) @on_trait_change( 'x_label, y_label, x_lower, x_upper, z_lower, z_upper, flip_order') def _trigger_redraw(self): self.quality = 1 self.redraw(replot=True) def _z_label_changed(self): self.z_labels[self.scale] = self.z_label self._trigger_redraw() def redraw(self, replot=False, now=False): if not now and self._draw_pending: self._redraw_timer.Restart() return #import wx canvas = self.figure.canvas if canvas is None: return def _draw(): self._callback_object._on_redraw(drawing=True) if replot: self._plot(self.x, self.y, self.z, self.scale) else: canvas.draw() self._draw_pending = False self._callback_object._on_redraw(drawing=False) if now: _draw() else: _draw() #self._redraw_timer = wx.CallLater(250, _draw) #self._draw_pending = True #self._redraw_timer.Start() # def _prepare_data(self, datasets): def _prepare_data(self, stack): # stack = stack_datasets(datasets) x = stack[:, :, 0] z = stack[:, :, 1] # y = array([ [i]*z.shape[1] for i in range(1, len(datasets) + 1) ]) y = array([[i] * z.shape[1] for i in range(1, stack.shape[1] + 1)]) if x[0, 0] < x[0, -1]: self.x_lower = x[0, 0] self.x_upper = x[0, -1] else: self.x_lower = x[0, -1] self.x_upper = x[0, 0] self.z_upper = z.max() return x, y, z def _plot(self, x, y, z, scale='linear'): self.x, self.y, self.z = x, y, z x, y, z = x.copy(), y.copy(), z.copy() if self.flip_order: z = z[::-1] self.scale = scale self.figure.clear() self.figure.set_facecolor('white') ax = self.ax = self.figure.add_subplot(111, projection='3d') ax.set_xlabel(self.x_label) ax.set_ylabel(self.y_label) self.z_label = self.z_labels[self.scale] ax.set_zlabel(self.z_label) y_rows = z.shape[0] ax.locator_params(axis='y', nbins=10, integer=True) ax.view_init(azim=self.azimuth, elev=self.elevation) if self.quality != MAX_QUALITY: # map quality from 1->5 to 0.05->0.5 to approx. no. of samples samples = int(z.shape[1] * ((self.quality - 1) * (0.5 - 0.05) / (5 - 1) + 0.05)) z, truncate_at, bins = rebin_preserving_peaks(z, samples / 2) # Take the x's from the original x's to maintain visual x-spacing # We need to calculate the x's for the rebinned data x0_row = x[0, :truncate_at] old_xs = np.linspace(x0_row.min(), x0_row.max(), bins * 2) new_xs = np.interp( old_xs, np.linspace(x0_row.min(), x0_row.max(), len(x0_row)), x0_row) x = np.tile(new_xs, (y.shape[0], 1)) # Set values to inf to avoid rendering by matplotlib x[(x < self.x_lower) | (x > self.x_upper)] = np.inf z[(z < self.z_lower) | (z > self.z_upper)] = np.inf # separate series with open lines ys = y[:, 0] points = [] for x_row, z_row in zip(x, z): points.append(zip(x_row, z_row)) lines = LineCollection(points) ax.add_collection3d(lines, zs=ys, zdir='y') ax.set_xlim3d(self.x_lower, self.x_upper) ax.set_ylim3d(1, y_rows) ax.set_zlim3d(self.z_lower, self.z_upper) self.figure.canvas.draw() return None def copy_to_clipboard(self): self.figure.canvas.Copy_to_Clipboard() def save_as(self, filename): self.figure.canvas.print_figure(filename) logger.logger.info('Saved plot {}'.format(filename)) def _reset_view(self): self.azimuth = -70 self.elevation = 30
class JumpmanViewer(BitmapViewer): name = "jumpman" pretty_name = "Jumpman Level Editor" control_cls = JumpmanGridControl has_bitmap = True has_colors = True has_zoom = True zoom_text = "bitmap zoom factor" has_caret = False ##### Traits can_select_objects = Bool(False) can_erase_objects = Bool(False) ##### class attributes valid_mouse_modes = [jm.AnticDSelectMode, jm.DrawGirderMode, jm.DrawLadderMode, jm.DrawUpRopeMode, jm.DrawDownRopeMode, jm.DrawPeanutMode, jm.EraseGirderMode, jm.EraseLadderMode, jm.EraseRopeMode, jm.JumpmanRespawnMode] default_mouse_mode_cls = jm.AnticDSelectMode #### Default traits def _machine_default(self): return JumpmanMachine() def _draw_pattern_default(self): return [0] ##### Properties @property def window_title(self): return "Jumpman Level Editor" @property def current_level(self): return self.linked_base.jumpman_playfield_model ##### Initialization and serialization def from_metadata_dict_post(self, e): # ignore bitmap renderer in restore because we always want to use the # JumpmanPlayfieldRenderer in Jumpman level edit mode if 'assembly_source' in e: self.current_level.assembly_source = e['assembly_source'] if 'old_trigger_mapping' in e: self.current_level.old_trigger_mapping = e['old_trigger_mapping'] def to_metadata_dict_post(self, mdict, document): mdict["assembly_source"] = self.current_level.assembly_source mdict["old_trigger_mapping"] = dict(self.current_level.old_trigger_mapping) # so we don't try to pickle a TraitDictObject def get_extra_segment_savers(self, segment): """Hook to provide additional ways to save the data based on this view of the data """ return [js.JumpmanSaveAsATR, js.JumpmanSaveAsXEX] ##### Trait change handlers def recalc_data_model(self): self.current_level.init_level_builder(self) self.machine.update_colors(self.current_level.level_colors) @on_trait_change('machine.bitmap_shape_change_event,machine.bitmap_color_change_event') def update_bitmap(self, evt): log.debug("BitmapViewer: machine bitmap changed for %s" % self.control) if evt is not Undefined: self.control.recalc_view() self.linked_base.editor.update_pane_names() @on_trait_change('linked_base.editor.document.byte_values_changed') def byte_values_changed(self, index_range): log.debug("byte_values_changed: %s index_range=%s" % (self, str(index_range))) if index_range is not Undefined: self.control.recalc_view() @on_trait_change('linked_base.jumpman_trigger_selected_event') def do_jumpman_trigger_selected_event(self, new_trigger_root): log.debug("jumpman_trigger_selected_changed: %s selected=%s" % (self, str(new_trigger_root))) if new_trigger_root is not Undefined: self.set_trigger_view(new_trigger_root) ##### Jumpman level construction def update_harvest_state(self): if not self.current_level.valid_level: return harvest_state = self.current_level.level_builder.get_harvest_state() self.num_ladders = len(harvest_state.ladder_positions) self.num_downropes = len(harvest_state.downrope_positions) self.num_peanuts = len(harvest_state.peanuts) # FIXME: force redraw of level data here because it depends on the # level builder objects so it can count the number of items #self.level_data.refresh_view() #self.trigger_list.refresh_view() def set_trigger_view(self, trigger_root): level = self.current_level mouse_mode = level.mouse_mode if mouse_mode.can_paste: mouse_mode.objects = [] level.set_trigger_root(trigger_root) self.can_erase_objects = trigger_root is not None self.control.refresh_view()
class TraitGridModel(GridModel): """ A TraitGridModel builds a grid from a list of traits objects. Each row represents on object, each column one trait from those objects. All the objects must be of the same type. Optionally a user may pass in a list of trait names defining which traits will be shown in the columns and in which order. If this list is not passed in, then the first object is inspected and every trait from that object gets a column.""" # A 2-dimensional list/array containing the grid data. data = List() #HasTraits) # The column definitions columns = Trait(None, None, List(Trait(None, Str, TraitGridColumn))) # The trait to look at to get the row name row_name_trait = Trait(None, None, Str) # Allow column sorting? allow_column_sort = Bool(True) # A factory to generate new rows. If this is not None then it must # be a no-argument function. row_factory = Trait(None, None, Function) ######################################################################### # 'object' interface. ######################################################################### def __init__(self, **traits): """ Create a TraitGridModel object. """ # Base class constructor super(TraitGridModel, self).__init__(**traits) # if no columns are pass in then create the list of names # from the first trait in the list. if the list is empty, # the columns should be an empty list as well. self._auto_columns = self.columns if self.columns is None or len(self.columns) == 0: if self.data is not None and len(self.data) > 0: self._auto_columns = [] # we only add traits that aren't events, since events # are write-only for name, trait in self.data[0].traits().items(): if trait.type != 'event': self._auto_columns.append(TraitGridColumn(name=name)) else: self._auto_columns = [] # attach trait handlers to the list object self.on_trait_event(self._on_data_changed, 'data') self.on_trait_event(self._on_data_items_changed, 'data_items') # attach appropriate trait handlers to objects in the list self.__manage_data_listeners(self.data) # attach a listener to the column definitions so we refresh when # they change self.on_trait_change(self._on_columns_changed, 'columns') self.on_trait_event(self._on_columns_items_changed, 'columns_items') # attach listeners to the column definitions themselves self.__manage_column_listeners(self.columns) # attach a listener to the row_name_trait self.on_trait_change(self._on_row_name_trait_changed, 'row_name_trait') return ######################################################################### # 'GridModel' interface. ######################################################################### def get_column_count(self): """ Return the number of columns for this table. """ return len(self._auto_columns) def get_column_name(self, index): """ Return the label of the column specified by the (zero-based) index. """ try: name = col = self._auto_columns[index] if isinstance(col, TraitGridColumn): if col.label is not None: name = col.label else: name = col.name except IndexError: name = '' return name def get_column_size(self, index): """ Return the size in pixels of the column indexed by col. A value of -1 or None means use the default. """ size = -1 try: col = self._auto_columns[index] if isinstance(col, TraitGridColumn): size = col.size except IndexError: pass return size def get_cols_drag_value(self, cols): """ Return the value to use when the specified columns are dragged or copied and pasted. cols is a list of column indexes. """ # iterate over every column, building a list of the values in that # column value = [] for col in cols: value.append(self.__get_data_column(col)) return value def get_cols_selection_value(self, cols): """ Returns a list of TraitGridSelection objects containing the object corresponding to the grid rows and the traits corresponding to the specified columns. """ values = [] for obj in self.data: for col in cols: values.append( TraitGridSelection(obj=obj, trait_name=self.__get_column_name(col))) return values def sort_by_column(self, col, reverse=False): """ Sort model data by the column indexed by col. """ # first check to see if we allow sorts by column if not self.allow_column_sort: return # see if a sorter is specified for this column try: column = self._auto_columns[col] name = self.__get_column_name(col) # by default we use cmp to sort on the traits sorter = cmp if isinstance(column, TraitGridColumn) and \ column.sorter is not None: sorter = column.sorter except IndexError: return # now sort the data appropriately def sort_function(a, b): if hasattr(a, name): a_trait = getattr(a, name) else: a_trait = None if hasattr(b, name): b_trait = getattr(b, name) else: b_trait = None return sorter(a_trait, b_trait) self.data.sort(sort_function) if reverse: self.data.reverse() # now fire an event to tell the grid we're sorted print 'firing sort event' self.column_sorted = GridSortEvent(index=col, reversed=reverse) return def is_column_read_only(self, index): """ Return True if the column specified by the zero-based index is read-only. """ return self.__get_column_readonly(index) def get_row_count(self): """ Return the number of rows for this table. """ if self.data is not None: count = len(self.data) else: count = 0 return count def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) index. """ if self.row_name_trait is not None: try: row = self._get_row(index) if hasattr(row, self.row_name_trait): name = getattr(row, self.row_name_trait) except IndexError: name = str(index + 1) else: name = str(index + 1) return name def get_rows_drag_value(self, rows): """ Return the value to use when the specified rows are dragged or copied and pasted. rows is a list of row indexes. If there is only one row listed, return the corresponding trait object. If more than one row is listed then return a list of objects. """ # return a list of objects value = [] for index in rows: try: # note that we can't use get_value for this because it # sometimes returns strings instead of the actual value, # e.g. in cases where a float_format is specified value.append(self._get_row(index)) except IndexError: value.append(None) return value def get_rows_selection_value(self, rows): """ Returns a list of TraitGridSelection objects containing the object corresponding to the selected rows. """ values = [] for row_index in rows: values.append(TraitGridSelection(obj=self.data[row_index])) return values def is_row_read_only(self, index): """ Return True if the row specified by the zero-based index is read-only. """ return False def get_cell_editor(self, row, col): """ Return the editor for the specified cell. """ # print 'TraitGridModel.get_cell_editor row: ', row, ' col: ', col obj = self.data[row] trait_name = self.__get_column_name(col) trait = obj.base_trait(trait_name) if trait is None: return None factory = trait.get_editor() return TraitGridCellAdapter(factory, obj, trait_name, '') def get_cell_drag_value(self, row, col): """ Return the value to use when the specified cell is dragged or copied and pasted. """ # find the name of the column indexed by col # note that this code is the same as the get_value code but without # the potential string formatting column = self.__get_column(col) obj = self._get_row(row) value = self._get_data_from_row(obj, column) return value def get_cell_selection_value(self, row, col): """ Returns a TraitGridSelection object specifying the data stored in the table at (row, col). """ obj = self.data[row] trait_name = self.__get_column_name(col) return TraitGridSelection(obj=obj, trait_name=trait_name) def resolve_selection(self, selection_list): """ Returns a list of (row, col) grid-cell coordinates that correspond to the objects in objlist. For each coordinate, if the row is -1 it indicates that the entire column is selected. Likewise coordinates with a column of -1 indicate an entire row that is selected. For the TraitGridModel, the objects in objlist must be TraitGridSelection objects. """ cells = [] for selection in selection_list: try: row = self.data.index(selection.obj) except ValueError: continue column = -1 if selection.trait_name is not None: column = self._get_column_index_by_trait(selection.trait_name) if column is None: continue cells.append((row, column)) return cells def get_type(self, row, col): """ Return the value stored in the table at (row, col). """ typename = self.__get_column_typename(col) return typename def get_value(self, row, col): """ Return the value stored in the table at (row, col). """ value = self.get_cell_drag_value(row, col) formats = self.__get_column_formats(col) if value is not None and formats is not None and \ formats.has_key(type(value)) and \ formats[type(value)] is not None: try: format = formats[type(value)] if callable(format): value = format(value) else: value = format % value except TypeError: # not enough arguments? wrong kind of arguments? pass return value def is_cell_empty(self, row, col): """ Returns True if the cell at (row, col) has a None value, False otherwise.""" value = self.get_value(row, col) return value is None def is_cell_editable(self, row, col): """ Returns True if the cell at (row, col) is editable, False otherwise. """ return not self.is_column_read_only(col) ######################################################################### # protected 'GridModel' interface. ######################################################################### def _insert_rows(self, pos, num_rows): """ Inserts num_rows at pos and fires an event iff a factory method for new rows is defined. Otherwise returns 0. """ count = 0 if self.row_factory is not None: new_data = [] for i in range(num_rows): new_data.append(self.row_factory()) count = self._insert_rows_into_model(pos, new_data) self.rows_added = ('added', pos, new_data) return count def _delete_rows(self, pos, num_rows): """ Removes rows pos through pos + num_rows from the model. """ if pos + num_rows >= self.get_row_count(): num_rows = self.get_rows_count() - pos return self._delete_rows_from_model(pos, num_rows) def _set_value(self, row, col, value): """ Sets the value of the cell at (row, col) to value. Raises a ValueError if the value is vetoed or the cell at (row, col) does not exist. """ #print 'TraitGridModel._set_value: new: ', value new_rows = 0 # find the column indexed by col column = self.__get_column(col) obj = self._get_row(row) success = False if obj is not None: success = self._set_data_on_row(obj, column, value) else: # Add a new row. new_rows = self._insert_rows(self.get_row_count(), 1) if new_rows > 0: # now set the value on the new object obj = self._get_row(self.get_row_count() - 1) success = self._set_data_on_row(obj, column, value) if not success: # fixme: what do we do in this case? veto the set somehow? raise # an exception? pass return new_rows ######################################################################### # protected interface. ######################################################################### def _get_row(self, index): """ Return the object that corresponds to the row at index. Override this to handle very large data sets. """ return self.data[index] def _get_data_from_row(self, row, column): """ Retrieve the data specified by column for this row. Attribute can be either a member of the row object, or a no-argument method on that object. Override this method to provide alternative ways of accessing the data in the object. """ value = None if row is not None and column is not None: if not isinstance(column, TraitGridColumn): # first handle the case where the column # definition might be just a string if hasattr(row, column): value = getattr(row, column) elif column.name is not None and hasattr(row, column.name): # this is the case when the trait name is specified value = getattr(row, column.name) elif column.method is not None and hasattr(row, column.method): # this is the case when an object method is specified value = getattr(row, column.method)() return value def _set_data_on_row(self, row, column, value): """ Retrieve the data specified by column for this row. Attribute can be either a member of the row object, or a no-argument method on that object. Override this method to provide alternative ways of accessing the data in the object. """ success = False if row is not None and column is not None: if not isinstance(column, TraitGridColumn): if hasattr(row, column): # sometimes the underlying grid gives us 0/1 instead # of True/False. do some conversion here to make that # case worl. #if type(getattr(row, column)) == bool and \ # type(value) != bool: # convert the value to a boolean # value = bool(value) setattr(row, column, value) success = True elif column.name is not None and hasattr(row, column.name): # sometimes the underlying grid gives us 0/1 instead # of True/False. do some conversion here to make that # case worl. #if type(getattr(row, column.name)) == bool and \ # type(value) != bool: # convert the value to a boolean # value = bool(value) setattr(row, column.name, value) success = True # do nothing in the method case as we don't allow rows # defined to return a method value to set the value return success def _insert_rows_into_model(self, pos, new_data): """ Insert the given new rows into the model. Override this method to handle very large data sets. """ for data in new_data: self.data.insert(pos, data) pos += 1 return def _delete_rows_from_model(self, pos, num_rows): """ Delete the specified rows from the model. Override this method to handle very large data sets. """ del self.data[pos, pos + num_rows] return num_rows ########################################################################### # trait handlers ########################################################################### def _on_row_name_trait_changed(self, new): """ Force the grid to refresh when any underlying trait changes. """ self.fire_content_changed() return def _on_columns_changed(self, object, name, old, new): """ Force the grid to refresh when any underlying trait changes. """ self.__manage_column_listeners(old, remove=True) self.__manage_column_listeners(self.columns) self._auto_columns = self.columns self.fire_structure_changed() return def _on_columns_items_changed(self, event): """ Force the grid to refresh when any underlying trait changes. """ self.__manage_column_listeners(event.removed, remove=True) self.__manage_column_listeners(event.added) self.fire_structure_changed() return def _on_contained_trait_changed(self, new): """ Force the grid to refresh when any underlying trait changes. """ self.fire_content_changed() return def _on_data_changed(self, object, name, old, new): """ Force the grid to refresh when the underlying list changes. """ self.__manage_data_listeners(old, remove=True) self.__manage_data_listeners(self.data) self.fire_structure_changed() return def _on_data_items_changed(self, event): """ Force the grid to refresh when the underlying list changes. """ # if an item was removed then remove that item's listener self.__manage_data_listeners(event.removed, remove=True) # if items were added then add trait change listeners on those items self.__manage_data_listeners(event.added) self.fire_content_changed() return ########################################################################### # private interface. ########################################################################### def __get_data_column(self, col): """ Return a 1-d list of data from the column indexed by col. """ row_count = self.get_row_count() coldata = [] for row in range(row_count): try: coldata.append(self.get_value(row, col)) except IndexError: coldata.append(None) return coldata def __get_column(self, col): try: column = self._auto_columns[col] except IndexError: column = None return column def __get_column_name(self, col): name = column = self.__get_column(col) if isinstance(column, TraitGridColumn): name = column.name return name def __get_column_typename(self, col): name = column = self.__get_column(col) typename = None if isinstance(column, TraitGridColumn): typename = column.typename return typename def __get_column_readonly(self, col): read_only = False column = self.__get_column(col) if isinstance(column, TraitGridColumn): read_only = column.read_only return read_only def __get_column_formats(self, col): formats = None column = self.__get_column(col) if isinstance(column, TraitGridColumn): formats = column.formats return formats def _get_column_index_by_trait(self, trait_name): cols = self._auto_columns for i in range(len(cols)): col = cols[i] if isinstance(col, TraitGridColumn): col_name = col.name else: col_name = col if col_name == trait_name: return i return None def __manage_data_listeners(self, list, remove=False): # attach appropriate trait handlers to objects in the list if list is not None: for item in list: item.on_trait_change(self._on_contained_trait_changed, remove=remove) return def __manage_column_listeners(self, collist, remove=False): if collist is not None: for col in collist: if isinstance(col, TraitGridColumn): col.on_trait_change(self._on_columns_changed, remove=remove) return
class NonInterruptibleTaskExample(HasStrictTraits): #: The executor to submit tasks to. traits_executor = Instance(TraitsExecutor) #: The future object returned on task submission. future = Instance(CallFuture) #: Number of points to use. sample_count = Int(10**8) #: Message about state of calculation. message = Str("No previous calculation runs") #: Button to calculate, plus its enabled state. calculate = Button() can_calculate = Property(Bool(), observe="future") #: Button to cancel, plus its enabled state. cancel = Button() can_cancel = Property(Bool(), observe="future.cancellable") @observe("calculate") def _submit_calculation(self, event): self.message = "Calculating π" self.future = submit_call(self.traits_executor, approximate_pi, self.sample_count) @observe("cancel") def _request_cancellation(self, event): self.future.cancel() self.message = "Cancelling" @observe("future:done") def _report_result(self, event): if self.future.state == CANCELLED: self.message = "Cancelled" elif self.future.state == FAILED: self.message = f"Unexpected error: {self.future.exception[1]}" elif self.future.state == COMPLETED: self.message = f"Complete: π ≈ {self.future.result:.6f}" else: # Shouldn't ever get here: CANCELLED, FAILED and COMPLETED # are the only possible final states of a future. assert False, f"Impossible state: {self.future.state}" self.future = None def _get_can_calculate(self): return self.future is None def _get_can_cancel(self): return self.future is not None and self.future.cancellable traits_view = View( Item("sample_count"), UItem("message", style="readonly"), HGroup( UItem("calculate", enabled_when="can_calculate"), UItem("cancel", enabled_when="can_cancel"), ), resizable=True, )
class PlotAxis(AbstractOverlay): """ The PlotAxis is a visual component that can be rendered on its own as a standalone component or attached as an overlay to another component. (To attach it as an overlay, set its **component** attribute.) When it is attached as an overlay, it draws into the padding around the component. """ # The mapper that drives this axis. mapper = Instance(AbstractMapper) # The text of the axis title. title = Trait('', Str, Unicode) #May want to add PlotLabel option # The font of the title. title_font = KivaFont('modern 12') # The spacing between the axis line and the title title_spacing = Trait('auto', 'auto', Float) # The color of the title. title_color = ColorTrait("black") # The thickness (in pixels) of each tick. tick_weight = Float(1.0) # The color of the ticks. tick_color = ColorTrait("black") # The font of the tick labels. tick_label_font = KivaFont('modern 10') # The color of the tick labels. tick_label_color = ColorTrait("black") # The rotation of the tick labels. tick_label_rotate_angle = Float(0) # Whether to align to corners or edges (corner is better for 45 degree rotation) tick_label_alignment = Enum('edge', 'corner') # The margin around the tick labels. tick_label_margin = Int(2) # The distance of the tick label from the axis. tick_label_offset = Float(8.) # Whether the tick labels appear to the inside or the outside of the plot area tick_label_position = Enum("outside", "inside") # A callable that is passed the numerical value of each tick label and # that returns a string. tick_label_formatter = Callable(DEFAULT_TICK_FORMATTER) # The number of pixels by which the ticks extend into the plot area. tick_in = Int(5) # The number of pixels by which the ticks extend into the label area. tick_out = Int(5) # Are ticks visible at all? tick_visible = Bool(True) # The dataspace interval between ticks. tick_interval = Trait('auto', 'auto', Float) # A callable that implements the AbstractTickGenerator interface. tick_generator = Instance(AbstractTickGenerator) # The location of the axis relative to the plot. This determines where # the axis title is located relative to the axis line. orientation = Enum("top", "bottom", "left", "right") # Is the axis line visible? axis_line_visible = Bool(True) # The color of the axis line. axis_line_color = ColorTrait("black") # The line thickness (in pixels) of the axis line. axis_line_weight = Float(1.0) # The dash style of the axis line. axis_line_style = LineStyle('solid') # A special version of the axis line that is more useful for geophysical # plots. small_haxis_style = Bool(False) # Does the axis ensure that its end labels fall within its bounding area? ensure_labels_bounded = Bool(False) # Does the axis prevent the ticks from being rendered outside its bounds? # This flag is off by default because the standard axis *does* render ticks # that encroach on the plot area. ensure_ticks_bounded = Bool(False) # Fired when the axis's range bounds change. updated = Event #------------------------------------------------------------------------ # Override default values of inherited traits #------------------------------------------------------------------------ # Background color (overrides AbstractOverlay). Axes usually let the color of # the container show through. bgcolor = ColorTrait("transparent") # Dimensions that the axis is resizable in (overrides PlotComponent). # Typically, axes are resizable in both dimensions. resizable = "hv" #------------------------------------------------------------------------ # Private Traits #------------------------------------------------------------------------ # Cached position calculations _tick_list = List # These are caches of their respective positions _tick_positions = Any #List _tick_label_list = Any _tick_label_positions = Any _tick_label_bounding_boxes = List _major_axis_size = Float _minor_axis_size = Float _major_axis = Array _title_orientation = Array _title_angle = Float _origin_point = Array _inside_vector = Array _axis_vector = Array _axis_pixel_vector = Array _end_axis_point = Array ticklabel_cache = List _cache_valid = Bool(False) #------------------------------------------------------------------------ # Public methods #------------------------------------------------------------------------ def __init__(self, component=None, **kwargs): # TODO: change this back to a factory in the instance trait some day self.tick_generator = DefaultTickGenerator() # Override init so that our component gets set last. We want the # _component_changed() event handler to get run last. super(PlotAxis, self).__init__(**kwargs) if component is not None: self.component = component def invalidate(self): """ Invalidates the pre-computed layout and scaling data. """ self._reset_cache() self.invalidate_draw() return def traits_view(self): """ Returns a View instance for use with Traits UI. This method is called automatically be the Traits framework when .edit_traits() is invoked. """ from axis_view import AxisView return AxisView #------------------------------------------------------------------------ # PlotComponent and AbstractOverlay interface #------------------------------------------------------------------------ def _do_layout(self, *args, **kw): """ Tells this component to do layout at a given size. Overrides Component. """ if self.use_draw_order and self.component is not None: self._layout_as_overlay(*args, **kw) else: super(PlotAxis, self)._do_layout(*args, **kw) return def overlay(self, component, gc, view_bounds=None, mode='normal'): """ Draws this component overlaid on another component. Overrides AbstractOverlay. """ if not self.visible: return self._draw_component(gc, view_bounds, mode, component) return def _draw_overlay(self, gc, view_bounds=None, mode='normal'): """ Draws the overlay layer of a component. Overrides PlotComponent. """ self._draw_component(gc, view_bounds, mode) return def _draw_component(self, gc, view_bounds=None, mode='normal', component=None): """ Draws the component. This method is preserved for backwards compatibility. Overrides PlotComponent. """ if not self.visible: return if not self._cache_valid: if component is not None: self._calculate_geometry_overlay(component) else: self._calculate_geometry() self._compute_tick_positions(gc, component) self._compute_labels(gc) with gc: # slight optimization: if we set the font correctly on the # base gc before handing it in to our title and tick labels, # their set_font() won't have to do any work. gc.set_font(self.tick_label_font) if self.axis_line_visible: self._draw_axis_line(gc, self._origin_point, self._end_axis_point) if self.title: self._draw_title(gc) self._draw_ticks(gc) self._draw_labels(gc) self._cache_valid = True return #------------------------------------------------------------------------ # Private draw routines #------------------------------------------------------------------------ def _layout_as_overlay(self, size=None, force=False): """ Lays out the axis as an overlay on another component. """ if self.component is not None: if self.orientation in ("left", "right"): self.y = self.component.y self.height = self.component.height if self.orientation == "left": self.width = self.component.padding_left self.x = self.component.outer_x elif self.orientation == "right": self.width = self.component.padding_right self.x = self.component.x2 + 1 else: self.x = self.component.x self.width = self.component.width if self.orientation == "bottom": self.height = self.component.padding_bottom self.y = self.component.outer_y elif self.orientation == "top": self.height = self.component.padding_top self.y = self.component.y2 + 1 return def _draw_axis_line(self, gc, startpoint, endpoint): """ Draws the line for the axis. """ with gc: gc.set_antialias(0) gc.set_line_width(self.axis_line_weight) gc.set_stroke_color(self.axis_line_color_) gc.set_line_dash(self.axis_line_style_) gc.move_to(*around(startpoint)) gc.line_to(*around(endpoint)) gc.stroke_path() return def _draw_title(self, gc, label=None, axis_offset=None): """ Draws the title for the axis. """ if label is None: title_label = Label(text=self.title, font=self.title_font, color=self.title_color, rotate_angle=self.title_angle) else: title_label = label # get the _rotated_ bounding box of the label tl_bounds = array(title_label.get_bounding_box(gc), float64) text_center_to_corner = -tl_bounds / 2.0 # which axis are we moving away from the axis line along? axis_index = self._major_axis.argmin() if self.title_spacing != 'auto': axis_offset = self.title_spacing if (self.title_spacing) and (axis_offset is None): if not self.ticklabel_cache: axis_offset = 25 else: axis_offset = max([ l._bounding_box[axis_index] for l in self.ticklabel_cache ]) * 1.3 offset = (self._origin_point + self._end_axis_point) / 2 axis_dist = self.tick_out + tl_bounds[axis_index] / 2.0 + axis_offset offset -= self._inside_vector * axis_dist offset += text_center_to_corner gc.translate_ctm(*offset) title_label.draw(gc) gc.translate_ctm(*(-offset)) return def _draw_ticks(self, gc): """ Draws the tick marks for the axis. """ if not self.tick_visible: return gc.set_stroke_color(self.tick_color_) gc.set_line_width(self.tick_weight) gc.set_antialias(False) gc.begin_path() tick_in_vector = self._inside_vector * self.tick_in tick_out_vector = self._inside_vector * self.tick_out for tick_pos in self._tick_positions: gc.move_to(*(tick_pos + tick_in_vector)) gc.line_to(*(tick_pos - tick_out_vector)) gc.stroke_path() return def _draw_labels(self, gc): """ Draws the tick labels for the axis. """ # which axis are we moving away from the axis line along? axis_index = self._major_axis.argmin() inside_vector = self._inside_vector if self.tick_label_position == "inside": inside_vector = -inside_vector for i in range(len(self._tick_label_positions)): #We want a more sophisticated scheme than just 2 decimals all the time ticklabel = self.ticklabel_cache[i] tl_bounds = self._tick_label_bounding_boxes[i] #base_position puts the tick label at a point where the vector #extending from the tick mark inside 8 units #just touches the rectangular bounding box of the tick label. #Note: This is not necessarily optimal for non #horizontal/vertical axes. More work could be done on this. base_position = self._tick_label_positions[i].copy() axis_dist = self.tick_label_offset + tl_bounds[axis_index] / 2.0 base_position -= inside_vector * axis_dist base_position -= tl_bounds / 2.0 if self.tick_label_alignment == 'corner': if self.orientation in ("top", "bottom"): base_position[0] += tl_bounds[0] / 2.0 elif self.orientation == "left": base_position[1] -= tl_bounds[1] / 2.0 elif self.orientation == "right": base_position[1] += tl_bounds[1] / 2.0 if self.ensure_labels_bounded: bound_idx = self._major_axis.argmax() if i == 0: base_position[bound_idx] = max( base_position[bound_idx], self._origin_point[bound_idx]) elif i == len(self._tick_label_positions) - 1: base_position[bound_idx] = min(base_position[bound_idx], self._end_axis_point[bound_idx] - \ tl_bounds[bound_idx]) tlpos = around(base_position) gc.translate_ctm(*tlpos) ticklabel.draw(gc) gc.translate_ctm(*(-tlpos)) return #------------------------------------------------------------------------ # Private methods for computing positions and layout #------------------------------------------------------------------------ def _reset_cache(self): """ Clears the cached tick positions, labels, and label positions. """ self._tick_positions = [] self._tick_label_list = [] self._tick_label_positions = [] return def _compute_tick_positions(self, gc, overlay_component=None): """ Calculates the positions for the tick marks. """ if (self.mapper is None): self._reset_cache() self._cache_valid = True return datalow = self.mapper.range.low datahigh = self.mapper.range.high screenhigh = self.mapper.high_pos screenlow = self.mapper.low_pos if overlay_component is not None: origin = getattr(overlay_component, 'origin', 'bottom left') if self.orientation in ("top", "bottom"): if "right" in origin: flip_from_gc = True else: flip_from_gc = False elif self.orientation in ("left", "right"): if "top" in origin: flip_from_gc = True else: flip_from_gc = False if flip_from_gc: screenlow, screenhigh = screenhigh, screenlow if (datalow == datahigh) or (screenlow == screenhigh) or \ (datalow in [inf, -inf]) or (datahigh in [inf, -inf]): self._reset_cache() self._cache_valid = True return if datalow > datahigh: raise RuntimeError, "DataRange low is greater than high; unable to compute axis ticks." if not self.tick_generator: return if hasattr(self.tick_generator, "get_ticks_and_labels"): # generate ticks and labels simultaneously tmp = self.tick_generator.get_ticks_and_labels( datalow, datahigh, screenlow, screenhigh) if len(tmp) == 0: tick_list = [] labels = [] else: tick_list, labels = tmp # compute the labels here self.ticklabel_cache = [Label(text=lab, font=self.tick_label_font, color=self.tick_label_color) \ for lab in labels] self._tick_label_bounding_boxes = [array(ticklabel.get_bounding_box(gc), float64) \ for ticklabel in self.ticklabel_cache] else: scale = 'log' if isinstance(self.mapper, LogMapper) else 'linear' if self.small_haxis_style: tick_list = array([datalow, datahigh]) else: tick_list = array( self.tick_generator.get_ticks(datalow, datahigh, datalow, datahigh, self.tick_interval, use_endpoints=False, scale=scale), float64) mapped_tick_positions = (array(self.mapper.map_screen(tick_list))-screenlow) / \ (screenhigh-screenlow) self._tick_positions = around(array([self._axis_vector*tickpos + self._origin_point \ for tickpos in mapped_tick_positions])) self._tick_label_list = tick_list self._tick_label_positions = self._tick_positions return def _compute_labels(self, gc): """Generates the labels for tick marks. Waits for the cache to become invalid. """ # tick labels are already computed if hasattr(self.tick_generator, "get_ticks_and_labels"): return formatter = self.tick_label_formatter def build_label(val): tickstring = formatter(val) if formatter is not None else str(val) return Label(text=tickstring, font=self.tick_label_font, color=self.tick_label_color, rotate_angle=self.tick_label_rotate_angle, margin=self.tick_label_margin) self.ticklabel_cache = [ build_label(val) for val in self._tick_label_list ] self._tick_label_bounding_boxes = [ array(ticklabel.get_bounding_box(gc), float) for ticklabel in self.ticklabel_cache ] return def _calculate_geometry(self): screenhigh = self.mapper.high_pos screenlow = self.mapper.low_pos if self.orientation in ('top', 'bottom'): self._major_axis_size = self.bounds[0] self._minor_axis_size = self.bounds[1] self._major_axis = array([1., 0.]) self._title_orientation = array([0., 1.]) self.title_angle = 0.0 if self.orientation == 'top': self._origin_point = array( self.position) + self._major_axis * screenlow self._inside_vector = array([0., -1.]) else: #self.oriention == 'bottom' self._origin_point = array(self.position) + array( [0., self.bounds[1]]) + self._major_axis * screenlow self._inside_vector = array([0., 1.]) elif self.orientation in ('left', 'right'): self._major_axis_size = self.bounds[1] self._minor_axis_size = self.bounds[0] self._major_axis = array([0., 1.]) self._title_orientation = array([-1., 0]) if self.orientation == 'left': self._origin_point = array(self.position) + array( [self.bounds[0], 0.]) + self._major_axis * screenlow self._inside_vector = array([1., 0.]) self.title_angle = 90.0 else: #self.orientation == 'right' self._origin_point = array( self.position) + self._major_axis * screenlow self._inside_vector = array([-1., 0.]) self.title_angle = 270.0 if self.ensure_ticks_bounded: self._origin_point -= self._inside_vector * self.tick_in self._end_axis_point = ( screenhigh - screenlow) * self._major_axis + self._origin_point self._axis_vector = self._end_axis_point - self._origin_point # This is the vector that represents one unit of data space in terms of screen space. self._axis_pixel_vector = self._axis_vector / sqrt( dot(self._axis_vector, self._axis_vector)) return def _calculate_geometry_overlay(self, overlay_component=None): if overlay_component is None: overlay_component = self component_origin = getattr(overlay_component, "origin", 'bottom left') screenhigh = self.mapper.high_pos screenlow = self.mapper.low_pos if self.orientation in ('top', 'bottom'): self._major_axis_size = overlay_component.bounds[0] self._minor_axis_size = overlay_component.bounds[1] self._major_axis = array([1., 0.]) self._title_orientation = array([0., 1.]) self.title_angle = 0.0 if self.orientation == 'top': self._origin_point = array( [overlay_component.x, overlay_component.y2]) self._inside_vector = array([0.0, -1.0]) else: self._origin_point = array( [overlay_component.x, overlay_component.y]) self._inside_vector = array([0.0, 1.0]) if "right" in component_origin: screenlow, screenhigh = screenhigh, screenlow elif self.orientation in ('left', 'right'): self._major_axis_size = overlay_component.bounds[1] self._minor_axis_size = overlay_component.bounds[0] self._major_axis = array([0., 1.]) self._title_orientation = array([-1., 0]) if self.orientation == 'left': self._origin_point = array( [overlay_component.x, overlay_component.y]) self._inside_vector = array([1.0, 0.0]) self.title_angle = 90.0 else: self._origin_point = array( [overlay_component.x2, overlay_component.y]) self._inside_vector = array([-1.0, 0.0]) self.title_angle = 270.0 if "top" in component_origin: screenlow, screenhigh = screenhigh, screenlow if self.ensure_ticks_bounded: self._origin_point -= self._inside_vector * self.tick_in self._end_axis_point = ( screenhigh - screenlow) * self._major_axis + self._origin_point self._axis_vector = self._end_axis_point - self._origin_point # This is the vector that represents one unit of data space in terms of screen space. self._axis_pixel_vector = self._axis_vector / sqrt( dot(self._axis_vector, self._axis_vector)) return #------------------------------------------------------------------------ # Event handlers #------------------------------------------------------------------------ def _bounds_changed(self, old, new): super(PlotAxis, self)._bounds_changed(old, new) self._layout_needed = True self._invalidate() def _bounds_items_changed(self, event): super(PlotAxis, self)._bounds_items_changed(event) self._layout_needed = True self._invalidate() def _mapper_changed(self, old, new): if old is not None: old.on_trait_change(self.mapper_updated, "updated", remove=True) if new is not None: new.on_trait_change(self.mapper_updated, "updated") self._invalidate() def mapper_updated(self): """ Event handler that is bound to this axis's mapper's **updated** event """ self._invalidate() def _position_changed(self, old, new): super(PlotAxis, self)._position_changed(old, new) self._cache_valid = False def _position_items_changed(self, event): super(PlotAxis, self)._position_items_changed(event) self._cache_valid = False def _position_changed_for_component(self): self._cache_valid = False def _position_items_changed_for_component(self): self._cache_valid = False def _bounds_changed_for_component(self): self._cache_valid = False self._layout_needed = True def _bounds_items_changed_for_component(self): self._cache_valid = False self._layout_needed = True def _origin_changed_for_component(self): self._invalidate() def _updated_fired(self): """If the axis bounds changed, redraw.""" self._cache_valid = False return def _invalidate(self): self._cache_valid = False self.invalidate_draw() if self.component: self.component.invalidate_draw() return def _component_changed(self): if self.mapper is not None: # If there is a mapper set, just leave it be. return # Try to pick the most appropriate mapper for our orientation # and what information we can glean from our component. attrmap = { "left": ("ymapper", "y_mapper", "value_mapper"), "bottom": ("xmapper", "x_mapper", "index_mapper"), } attrmap["right"] = attrmap["left"] attrmap["top"] = attrmap["bottom"] component = self.component attr1, attr2, attr3 = attrmap[self.orientation] for attr in attrmap[self.orientation]: if hasattr(component, attr): self.mapper = getattr(component, attr) break return #------------------------------------------------------------------------ # The following event handlers just invalidate our previously computed # Label instances and backbuffer if any of our visual attributes change. # TODO: refactor this stuff and the caching of contained objects (e.g. Label) #------------------------------------------------------------------------ def _title_changed(self): self.invalidate_draw() if self.component: self.component.invalidate_draw() return def _anytrait_changed(self, name, old, new): """ For every trait that defines a visual attribute we just call _invalidate() when a change is made. """ invalidate_traits = [ 'title_font', 'title_spacing', 'title_color', 'tick_weight', 'tick_color', 'tick_label_font', 'tick_label_color', 'tick_label_rotate_angle', 'tick_label_alignment', 'tick_label_margin', 'tick_label_offset', 'tick_label_position', 'tick_label_formatter', 'tick_in', 'tick_out', 'tick_visible', 'tick_interval', 'tick_generator', 'orientation', 'axis_line_visible', 'axis_line_color', 'axis_line_weight', 'axis_line_style', 'small_haxis_style', 'ensure_labels_bounded', 'ensure_ticks_bounded', ] if name in invalidate_traits: self._invalidate() #------------------------------------------------------------------------ # Persistence-related methods #------------------------------------------------------------------------ def __getstate__(self): dont_pickle = [ '_tick_list', '_tick_positions', '_tick_label_list', '_tick_label_positions', '_tick_label_bounding_boxes', '_major_axis_size', '_minor_axis_size', '_major_axis', '_title_orientation', '_title_angle', '_origin_point', '_inside_vector', '_axis_vector', '_axis_pixel_vector', '_end_axis_point', '_ticklabel_cache', '_cache_valid' ] state = super(PlotAxis, self).__getstate__() for key in dont_pickle: if state.has_key(key): del state[key] return state def __setstate__(self, state): super(PlotAxis, self).__setstate__(state) self._mapper_changed(None, self.mapper) self._reset_cache() self._cache_valid = False return
class _DataFrameEditor(UIEditor): """ TraitsUI-based editor implementation for data frames """ #: Indicate that the editor is scrollable/resizable: scrollable = True #: Should column titles be displayed: show_titles = Bool(True) #: The tabular adapter being used for the editor view: adapter = Instance(DataFrameAdapter) # -- Private Methods ------------------------------------------------------ def _target_name(self, name): if name: return "object.object." + name else: return "" def _data_frame_view(self): """ Return the view used by the editor. """ return View( Item( self._target_name(self.name), id="tabular_editor", show_label=False, editor=TabularEditor( show_titles=self.factory.show_titles, editable=self.factory.editable, adapter=self.adapter, selected=self._target_name(self.factory.selected), selected_row=self._target_name(self.factory.selected_row), selectable=self.factory.selectable, multi_select=self.factory.multi_select, activated=self._target_name(self.factory.activated), activated_row=self._target_name( self.factory.activated_row ), # noqa clicked=self._target_name(self.factory.clicked), dclicked=self._target_name(self.factory.dclicked), scroll_to_row=self._target_name( self.factory.scroll_to_row ), # noqa scroll_to_row_hint=self.factory.scroll_to_row_hint, scroll_to_column=self._target_name( self.factory.scroll_to_column ), # noqa right_clicked=self._target_name( self.factory.right_clicked ), # noqa right_dclicked=self._target_name( self.factory.right_dclicked ), # noqa column_clicked=self._target_name( self.factory.column_clicked ), # noqa column_right_clicked=self._target_name( self.factory.column_right_clicked ), # noqa operations=self.factory.operations, update=self._target_name(self.factory.update), refresh=self._target_name(self.factory.refresh), ), ), id="data_frame_editor", resizable=True, ) def init_ui(self, parent): """ Creates the Traits UI for displaying the array. """ factory = self.factory if factory.columns != []: columns = [] for column in factory.columns: if isinstance(column, str): title = column column_id = column else: title, column_id = column if column_id not in self.value.columns: continue columns.append((title, column_id)) else: columns = [ (column_id, column_id) for column_id in self.value.columns ] if factory.show_index: index_name = self.value.index.name if index_name is None: index_name = "" columns.insert(0, (index_name, "index")) if factory.adapter is not None: self.adapter = factory.adapter self.adapter._formats = factory.formats self.adapter._fonts = factory.fonts if not self.adapter.columns: self.adapter.columns = columns else: self.adapter = DataFrameAdapter( columns=columns, _formats=factory.formats, _fonts=factory.fonts ) return self.edit_traits( view="_data_frame_view", parent=parent, kind="subpanel" )
class TreeItem(HasTraits): """ A generic base-class for items in a tree data structure. """ #### 'TreeItem' interface ################################################# # Does this item allow children? allows_children = Bool(True) # The item's children. children = List(Instance('TreeItem')) # Arbitrary data associated with the item. data = Any # Does the item have any children? has_children = Property(Bool) # The item's parent. parent = Instance('TreeItem') ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns the informal string representation of the object. """ if self.data is None: s = '' else: s = str(self.data) return s ########################################################################### # 'TreeItem' interface. ########################################################################### #### Properties ########################################################### # has_children def _get_has_children(self): """ True iff the item has children. """ return len(self.children) != 0 #### Methods ############################################################## def append(self, child): """ Appends a child to this item. This removes the child from its current parent (if it has one). """ return self.insert(len(self.children), child) def insert(self, index, child): """ Inserts a child into this item at the specified index. This removes the child from its current parent (if it has one). """ if child.parent is not None: child.parent.remove(child) child.parent = self self.children.insert(index, child) return child def remove(self, child): """ Removes a child from this item. """ child.parent = None self.children.remove(child) return child def insert_before(self, before, child): """ Inserts a child into this item before the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(before) self.insert(index, child) return (index, child) def insert_after(self, after, child): """ Inserts a child into this item after the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(after) self.insert(index + 1, child) return (index, child)
class RemoteCommandServer(ConfigLoadable): ''' ''' simulation = False _server = None repeater = Instance(CommandRepeater) processor = Instance( 'pychron.remote_hardware.command_processor.CommandProcessor') host = Str(enter_set=True, auto_set=False) port = Int(enter_set=True, auto_set=False) klass = Str loaded_port = None loaded_host = None packets_received = Int packets_sent = Int repeater_fails = Int cur_rpacket = String cur_spacket = String server_button = Event server_label = Property(depends_on='_running') _running = Bool(False) _connected = Bool(False) save = Button _dirty = Bool(False) run_time = Str led = Instance(LED, ()) use_ipc = True def _repeater_default(self): """ """ if globalv.use_ipc: c = CommandRepeater(logger_name='{}_repeater'.format(self.name), name=self.name, config_path=os.path.join( paths.root, 'servers', '{}.cfg'.format(self.name))) if c.bootstrap(): return c def _repeater_fails_changed(self, old, new): if new != 0: self.repeater.led.state = 0 def load(self, *args, **kw): """ """ config = self.get_configuration() if config: server_class = self.config_get(config, 'General', 'class') if server_class is None: return if server_class == 'IPCServer': path = self.config_get(config, 'General', 'path') if path is None: self.warning('Path not set. use path config value') return addr = path self.host = path if os.path.exists(path): os.remove(path) else: if LOCAL: host = 'localhost' else: host = self.config_get(config, 'General', 'host', optional=True) port = self.config_get(config, 'General', 'port', cast='int') if host is None: host = socket.gethostbyname(socket.gethostname()) if port is None: self.warning('Host or Port not set {}:{}'.format( host, port)) return elif port < 1024: self.warning('Port Numbers < 1024 not allowed') return addr = (host, port) self.host = host self.port = port if port else 0 self.loaded_host = host self.loaded_port = port self.klass = server_class[:3] ds = None if config.has_option('Requests', 'datasize'): ds = config.getint('Requests', 'datasize') ptype = self.config_get(config, 'Requests', 'type', optional=False) if ptype is None: return self.datasize = ds self.processor_type = ptype self._server = self.server_factory(server_class, addr, ptype, ds) # add links for link in self.config_get_options(config, 'Links'): # note links cannot be stopped self._server.add_link(link, self.config_get(config, 'Links', link)) if self._server is not None and self._server.connected: addr = self._server.server_address # saddr = '({})'.format(','.join(addr if isinstance(addr, tuple) else (addr,))) saddr = '({})'.format(addr) msg = '%s - %s' % (server_class, saddr) self.info(msg) self._connected = True return True else: self._connected = False self.warning('Cannot connect to {}'.format(addr)) def server_factory(self, klass, addr, ptype, ds): ''' ''' # from tcp_server import TCPServer # from udp_server import UDPServer module = __import__('pychron.messaging.{}_server'.format( klass[:3].lower()), fromlist=[klass]) factory = getattr(module, klass) # gdict = globals() # if handler in gdict: # handler_klass = gdict[handler] # server = gdict[server_class] if ds is None: ds = 2**10 # return server(self, ptype, ds, addr, handler_klass) return factory(self, ptype, ds, addr) def open(self): ''' ''' self._running = True # t = threading.Thread(target = self._server.serve_forever) t = threading.Thread(target=self.start_server) t.start() return True def start_server(self): SELECT_TIMEOUT = 1 # THREAD_LIMIT = 15 while self._running: try: readySocket = select.select([self._server.socket], [], [], SELECT_TIMEOUT) if readySocket[0]: self._server.handle_request() # if threading.activeCount() < THREAD_LIMIT: # self._server.handle_request() except: pass # self._server.socket.close() def shutdown(self): """ """ self._connected = False if self._server is not None: # self._server.shutdown() self._server.socket.close() self._running = False def traits_view(self): """ """ cparams = VGroup( HGroup( Item('led', show_label=False, editor=LEDEditor()), Item('server_button', show_label=False, editor=ButtonEditor(label_value='server_label'), enabled_when='_connected'), ), Item('host', visible_when='not _running'), Item('port', visible_when='not _running'), show_border=True, label='Connection', ) stats = Group(Item('packets_received', style='readonly'), Item('cur_rpacket', label='Received', style='readonly'), Item('packets_sent', style='readonly'), Item('cur_spacket', label='Sent', style='readonly'), Item('repeater_fails', style='readonly'), Item('run_time', style='readonly'), show_border=True, label='Statistics', visible_when='_connected') buttons = HGroup(Item('save', show_label=False, enabled_when='_dirty')) v = View( VGroup(cparams, stats, buttons), handler=RCSHandler, ) return v def _run_time_update(self): ''' ''' # t = datetime.datetime.now() - self.start_time # h = t.seconds / 3600 # m = (t.seconds % 3600) / 60 # s = (t.seconds % 3600) % 60 t, h, m, s = diff_timestamp(datetime.datetime.now(), self.start_time) rt = '{:02d}:{:02d}:{:02d}'.format(h, m, s) if t.days: rt = '{} {:02d}:{:02d}:{:02d}'.format(t.days, h, m, s) self.run_time = rt def __running_changed(self): ''' ''' if self._running: self.start_time = datetime.datetime.now() self.timer = Timer(1000, self._run_time_update) else: self.timer.Stop() def _anytrait_changed(self, name, value): ''' ''' if name in ['host', 'port']: attr = 'loaded_{}'.format(name) a = getattr(self, attr) if value != a and a is not None: self._dirty = True def _save_fired(self): ''' ''' self.shutdown() config = self.get_configuration() for attr in ['host', 'port']: a = getattr(self, attr) setattr(self, 'loaded_{}'.format(attr), a) config.set('General', attr, a) self.write_configuration(config) self.load() self._dirty = False def _server_button_fired(self): ''' ''' if self._running: self.shutdown() else: # reset the stats self.packets_received = 0 self.packets_sent = 0 self.cur_rpacket = '' self.cur_spacket = '' self.repeater_fails = 0 # self._server = self.server_factory('TCPServer', # (self.host, self.port), # self.handler, # self.processor_type, # self.datasize # ) # self.open() self.bootstrap() def _get_server_label(self): ''' ''' return 'Start' if not self._running else 'Stop'
class TestCase(Mayavi): """ This class is to be subclassed when you write a test. """ # Interact with the user after test is done? Normally tests just # exit after completion, this prevents that. interact = Bool(False) # Always use offscreen rendering to generate images -- even if # `self.compare_image` was called in the test.. offscreen = Bool(False) # Use the standalone mode. This mode does not use the envisage Mayavi # application. standalone = Bool(True) app_window = Instance('pyface.api.ApplicationWindow') gui = Instance('pyface.gui.GUI') # An exception info if an exception was raised by a test. exception_info = Any ###################################################################### # `Mayavi` interface. ###################################################################### def main(self, argv=None, plugins=None): """Overridden main method that sets the argv to sys.argv[1:] by default. Call this to run the test. """ if argv is None: argv = sys.argv[1:] if not is_running_with_nose(): self.parse_command_line(argv) if self.standalone: self.run_standalone() else: # Call the superclass main method. super(TestCase, self).main(argv, plugins) def setup_logger(self): """Overridden logger setup.""" if self.standalone: path = os.path.join(ETSConfig.application_data, 'mayavi_e3', 'mayavi-test.log') path = os.path.abspath(path) log_path = os.path.dirname(path) if not os.path.exists(log_path): os.makedirs(log_path) else: path = 'mayavi-test.log' setup_logger(logger, path, mode=self.log_mode) def run_standalone(self): from mayavi.core.engine import Engine from mayavi.plugins.script import Script from pyface.api import ApplicationWindow, GUI self.setup_logger() if self.offscreen: engine = Engine(scene_factory=off_screen_viewer) else: engine = Engine() engine.start() self.exception_info = None self.script = Script(engine=engine) self.gui = g = GUI() self.app_window = a = ApplicationWindow() a.open() a.show(False) g.invoke_later(self.run) g.start_event_loop() if self.exception_info is not None: type, value, tb = self.exception_info raise type, value, tb def run(self): """This starts everything up and runs the test. Call main to run the test.""" # Calls the users test code. try: self.do() except Exception, e: type, value, tb = sys.exc_info() if is_running_with_nose(): self.exception_info = type, value, tb else: # To mimic behavior of unittest. sys.stderr.write('\nfailures=1\n') info = traceback.extract_tb(tb) filename, lineno, function, text = info[-1] # last line only exc_msg = "%s\nIn %s:%d\n%s: %s (in %s)" %\ ('Exception', filename, lineno, type.__name__, str(value), function) sys.stderr.write(exc_msg + '\n') # Log the message. logger.exception(exc_msg) if not self.interact: sys.exit(1) finally:
class SettingsView(HasTraits): """Traits-defined console settings view. link : object Serial driver object. read_finished_functions : list Callbacks to call on finishing a settings read. name_of_yaml_file : str Settings to read from (defaults to settings.yaml) expert : bool Show expert settings (defaults to False) gui_mode : bool ??? (defaults to True) skip : bool Skip reading of the settings (defaults to False). Intended for use when reading from network connections. """ show_auto_survey = Bool(False) settings_yaml = list() auto_survey = SVGButton( label='Auto Survey', tooltip='Auto populate surveyed lat, lon and alt fields', filename='', width=16, height=20) settings_read_button = SVGButton( label='Reload', tooltip='Reload settings from Piksi', filename=resource_filename('console/images/fontawesome/refresh.svg'), width=16, height=20) settings_save_button = SVGButton( label='Save to Flash', tooltip='Save settings to Flash', filename=resource_filename('console/images/fontawesome/download.svg'), width=16, height=20) factory_default_button = SVGButton( label='Reset to Defaults', tooltip='Reset to Factory Defaults', filename=resource_filename( 'console/images/fontawesome/exclamation-triangle.svg'), width=16, height=20) settings_list = List(SettingBase) expert = Bool() selected_setting = Instance(SettingBase) traits_view = View( HSplit( Item( 'settings_list', editor=TabularEditor(adapter=SimpleAdapter(), editable_labels=False, auto_update=True, editable=False, selected='selected_setting'), show_label=False, ), VGroup( HGroup( Item('settings_read_button', show_label=False), Item('settings_save_button', show_label=False), Item('factory_default_button', show_label=False), Item('auto_survey', show_label=False, visible_when='show_auto_survey'), ), HGroup( Item('expert', label="Show Advanced Settings", show_label=True)), Item('selected_setting', style='custom', show_label=False), ), )) def _selected_setting_changed(self): if self.selected_setting: if (self.selected_setting.name in [ 'surveyed_position', 'broadcast', 'surveyed_lat', 'surveyed_lon', 'surveyed_alt' ] and self.lat != 0 and self.lon != 0): self.show_auto_survey = True else: self.show_auto_survey = False def _expert_changed(self, info): try: self.settings_display_setup(do_read_finished=False) except AttributeError: pass def _settings_read_button_fired(self): self.enumindex = 0 self.ordering_counter = 0 self.link(MsgSettingsReadByIndexReq(index=self.enumindex)) def _settings_save_button_fired(self): self.link(MsgSettingsSave()) def _factory_default_button_fired(self): confirm_prompt = prompt.CallbackPrompt( title="Reset to Factory Defaults?", actions=[prompt.close_button, prompt.reset_button], callback=self.reset_factory_defaults) confirm_prompt.text = "This will erase all settings and then reset the device.\n" \ + "Are you sure you want to reset to factory defaults?" confirm_prompt.run(block=False) def reset_factory_defaults(self): # Reset the Piksi, with flag set to restore default settings self.link(MsgReset(flags=1)) def _auto_survey_fired(self): confirm_prompt = prompt.CallbackPrompt( title="Auto populate surveyed position?", actions=[prompt.close_button, prompt.auto_survey_button], callback=self.auto_survey_fn) confirm_prompt.text = "\n" \ + "This will set the Surveyed Position section to the \n" \ + "mean position of the last 1000 position solutions.\n \n" \ + "The fields that will be auto-populated are: \n" \ + "Surveyed Lat \n" \ + "Surveyed Lon \n" \ + "Surveyed Alt \n \n" \ + "The surveyed position will be an approximate value. \n" \ + "This may affect the relative accuracy of Piksi. \n \n" \ + "Are you sure you want to auto-populate the Surveyed Position section?" confirm_prompt.run(block=False) def auto_survey_fn(self): lat_value = str(self.lat) lon_value = str(self.lon) alt_value = str(self.alt) self.settings['surveyed_position']['surveyed_lat'].value = lat_value self.settings['surveyed_position']['surveyed_lon'].value = lon_value self.settings['surveyed_position']['surveyed_alt'].value = alt_value self.settings_display_setup(do_read_finished=False) # Callbacks for receiving messages def settings_display_setup(self, do_read_finished=True): self.settings_list = [] sections = sorted(self.settings.keys()) for sec in sections: this_section = [] for name, setting in sorted(self.settings[sec].iteritems(), key=lambda n_s: n_s[1].ordering): if not setting.expert or (self.expert and setting.expert): this_section.append(setting) if this_section: self.settings_list.append(SectionHeading(sec)) self.settings_list += this_section # call read_finished_functions as needed if do_read_finished: for cb in self.read_finished_functions: if self.gui_mode: GUI.invoke_later(cb) else: cb() def settings_read_by_index_done_callback(self, sbp_msg, **metadata): self.settings_display_setup() def settings_read_resp_callback(self, sbp_msg, **metadata): confirmed_set = True settings_list = sbp_msg.setting.split("\0") if len(settings_list) <= 3: print("Received malformed settings read response {0}".format( sbp_msg)) confirmed_set = False try: if self.settings[settings_list[0]][ settings_list[1]].value != settings_list[2]: try: float_val = float(self.settings[settings_list[0]][ settings_list[1]].value) float_val2 = float(settings_list[2]) if abs(float_val - float_val2) > 0.000001: confirmed_set = False except ValueError: confirmed_set = False if not confirmed_set: pass # We pass if the new value doesn't match current console value. It would be nice to update it, but that may cause side effects. self.settings[settings_list[0]][ settings_list[1]].confirmed_set = confirmed_set except KeyError: return def settings_read_by_index_callback(self, sbp_msg, **metadata): section, setting, value, format_type = sbp_msg.payload[2:].split( '\0')[:4] self.ordering_counter += 1 if format_type == '': format_type = None else: setting_type, setting_format = format_type.split(':') if section not in self.settings: self.settings[section] = {} if format_type is None: # Plain old setting, no format information self.settings[section][setting] = Setting( setting, section, value, ordering=self.ordering_counter, settings=self) else: if setting_type == 'enum': enum_values = setting_format.split(',') self.settings[section][setting] = EnumSetting( setting, section, value, ordering=self.ordering_counter, values=enum_values, settings=self) else: # Unknown type, just treat is as a string self.settings[section][setting] = Setting(setting, section, value, settings=self) if self.enumindex == sbp_msg.index: self.enumindex += 1 self.link(MsgSettingsReadByIndexReq(index=self.enumindex)) def piksi_startup_callback(self, sbp_msg, **metadata): self.settings.clear() self._settings_read_button_fired() def set(self, section, name, value): self.link( MsgSettingsWrite(setting='%s\0%s\0%s\0' % (section, name, value))) def cleanup(self): """ Remove callbacks from serial link. """ self.link.remove_callback(self.piksi_startup_callback, SBP_MSG_STARTUP) self.link.remove_callback(self.settings_read_by_index_callback, SBP_MSG_SETTINGS_READ_BY_INDEX_REQ) self.link.remove_callback(self.settings_read_by_index_callback, SBP_MSG_SETTINGS_READ_BY_INDEX_RESP) self.link.remove_callback(self.settings_read_by_index_done_callback, SBP_MSG_SETTINGS_READ_BY_INDEX_DONE) def __enter__(self): return self def __exit__(self, *args): self.cleanup() def __init__(self, link, read_finished_functions=[], name_of_yaml_file="settings.yaml", expert=False, gui_mode=True, skip=False): super(SettingsView, self).__init__() self.expert = expert self.show_auto_survey = False self.gui_mode = gui_mode self.enumindex = 0 self.settings = {} self.link = link self.link.add_callback(self.piksi_startup_callback, SBP_MSG_STARTUP) self.link.add_callback(self.settings_read_by_index_callback, SBP_MSG_SETTINGS_READ_BY_INDEX_REQ) self.link.add_callback(self.settings_read_by_index_callback, SBP_MSG_SETTINGS_READ_BY_INDEX_RESP) self.link.add_callback(self.settings_read_by_index_done_callback, SBP_MSG_SETTINGS_READ_BY_INDEX_DONE) self.link.add_callback(self.settings_read_resp_callback, SBP_MSG_SETTINGS_READ_RESP) # Read in yaml file for setting metadata self.settings_yaml = SettingsList(name_of_yaml_file) # List of functions to be executed after all settings are read. # No support for arguments currently. self.read_finished_functions = read_finished_functions self.setting_detail = SettingBase() if not skip: try: self._settings_read_button_fired() except IOError: print( "IOError in settings_view startup call of _settings_read_button_fired." ) print("Verify that write permissions exist on the port.") self.python_console_cmds = {'settings': self}
class Action(HasTraits): """ The base class for all actions. An action is the non-UI side of a command which can be triggered by the end user. Actions are typically associated with buttons, menu items and tool bar tools. When the user triggers the command via the UI, the action's 'perform' method is invoked to do the actual work. """ #### 'Action' interface ################################################### # Keyboard accelerator (by default the action has NO accelerator). accelerator = Unicode # Is the action checked? This is only relevant if the action style is # 'radio' or 'toggle'. checked = Bool(False) # A longer description of the action (used for context sensitive help etc). # If no description is specified, the tooltip is used instead (and if there # is no tooltip, then well, maybe you just hate your users ;^). description = Unicode # Is the action enabled? enabled = Bool(True) # Is the action visible? visible = Bool(True) # The action's unique identifier (may be None). id = Str # The action's image (displayed on tool bar tools etc). image = Image # The action's name (displayed on menus/tool bar tools etc). name = Unicode # An (optional) callable that will be invoked when the action is performed. on_perform = Callable # The action's style. style = Enum('push', 'radio', 'toggle') # A short description of the action used for tooltip text etc. tooltip = Unicode ########################################################################### # 'Action' interface. ########################################################################### #### Initializers ######################################################### def _id_default(self): """ Initializes the 'id' trait. """ return self.name #### Methods ############################################################## def destroy(self): """ Called when the action is no longer required. By default this method does nothing, but this would be a great place to unhook trait listeners etc. """ return def perform(self, event): """ Performs the action. """ if self.on_perform is not None: self.on_perform() return
class IsotopeAnalysisSelector(DatabaseSelector): title = 'Recall Analyses' # orm_path = 'pychron.database.orms.isotope_orm' query_table = meas_AnalysisTable record_view_klass = IsotopeRecordView # record_klass = IsotopeRecord # record_klass = DummyIsotopeRecord query_klass = IsotopeQuery tabular_adapter = IsotopeResultsAdapter lookup = { 'Labnumber': ([gen_LabTable], gen_LabTable.identifier), 'Step': ([], meas_AnalysisTable.step), 'Aliquot': ([], meas_AnalysisTable.aliquot), 'Sample': ([gen_LabTable, gen_SampleTable], gen_SampleTable.name), 'Irradiation': ([ gen_LabTable, irrad_PositionTable, irrad_LevelTable, irrad_IrradiationTable ], irrad_IrradiationTable.name), 'Irradiation Level': ([ gen_LabTable, irrad_PositionTable, irrad_LevelTable, irrad_IrradiationTable ], irrad_LevelTable.name), 'Irradiation Position': ([ gen_LabTable, irrad_PositionTable, irrad_LevelTable, irrad_IrradiationTable ], irrad_PositionTable.position), 'Run Date/Time': ([], meas_AnalysisTable.analysis_timestamp), 'Project': ([ gen_LabTable, gen_SampleTable, gen_ProjectTable, ], gen_ProjectTable.name), 'Mass Spectrometer': ([meas_MeasurementTable, gen_MassSpectrometerTable], gen_MassSpectrometerTable.name), 'Analysis Type': ([meas_MeasurementTable, gen_AnalysisTypeTable], gen_AnalysisTypeTable.name) } mass_spectrometer = Str('Spectrometer') mass_spectrometers = Property analysis_type = Str('Analysis Type') analysis_types = Property omit_invalid = Bool(True) def _record_factory(self, idn): if isinstance(idn, meas_AnalysisTable): dbr = idn elif isinstance(idn, str): uuid = idn else: uuid = idn.uuid dbr = self.db.get_analysis(uuid, key='uuid') return self.record_klass(_dbrecord=dbr) def _get_selector_records(self, queries=None, limit=None, use_filters=True, **kw): with self.db.session_ctx() as sess: # sess = self.db.get_session() q = sess.query(meas_AnalysisTable) if self.omit_invalid: q = q.filter(meas_AnalysisTable.tag != 'invalid') # q = q.filter(meas_AnalysisTable.status != -1) if queries and use_filters: qs = self._build_filters() if qs: queries = queries + qs # queries.extend(qs) return self._get_records(q, queries, limit, timestamp='analysis_timestamp') def _get_mass_spectrometers(self): db = self.db mas = ['Spectrometer', LINE_STR] ms = db.get_mass_spectrometers() if ms: mas += [m.name for m in ms] return mas def _get_analysis_types(self): db = self.db ats = ['Analysis Type', LINE_STR] ai = db.get_analysis_types() if ai: ats += [aii.name.capitalize() for aii in ai] return ats def _analysis_type_changed(self): self._refresh_results() def _mass_spectrometer_changed(self): self._refresh_results() def _refresh_results(self): import inspect stack = inspect.stack() self.debug('refresh results by {}'.format(stack[1][3])) self.execute_query(load=False) def _build_filters(self): ma = self.mass_spectrometer an = self.analysis_type qs = [] # if pr != NULL_STR: # q = selector.query_factory(parameter='Project', criterion=pr) # qs.append(q) if ma not in ('Spectrometer', LINE_STR): q = self.query_factory(parameter='Mass Spectrometer', criterion=ma) qs.append(q) if an not in ('Analysis Type', LINE_STR): q = self.query_factory(parameter='Analysis Type', criterion=an) qs.append(q) return qs
class ObservationView(HasTraits): python_console_cmds = Dict() _obs_table_list = List() obs = Dict() name = 'Rover' recording = Bool(False) record_button = SVGButton( label='Record', tooltip='Record Raw Observations', toggle_tooltip='Stop Recording', toggle=True, filename=os.path.join(os.path.dirname(__file__), 'images', 'fontawesome', 'floppy-o.svg'), toggle_filename=os.path.join(os.path.dirname(__file__), 'images', 'fontawesome', 'stop.svg'), width=16, height=16 ) def trait_view(self, view): return View( HGroup( Item('_obs_table_list', style = 'readonly', editor = TabularEditor(adapter=SimpleAdapter()), show_label=False), VGroup( Item('record_button', show_label=False), ), label = self.name, show_border = True ) ) def _record_button_fired(self): self.recording = not self.recording if not self.recording: if self.rinex_file is not None: self.rinex_file.close() self.rinex_file = None def update_obs(self): self._obs_table_list = [(prn + 1,) + obs for prn, obs in sorted(self.obs.items(), key=lambda x: x[0])] def obs_callback(self, data, sender=None): if (sender is not None and (self.relay ^ (sender == 0))): return if self.rinex_file is None and self.recording: self.rinex_file = open(self.name+self.t.strftime("-%Y%m%d-%H%M%S.obs"), 'w') header = """ 2.11 OBSERVATION DATA G (GPS) RINEX VERSION / TYPE pyNEX %s UTC PGM / RUN BY / DATE MARKER NAME OBSERVER / AGENCY REC # / TYPE / VERS ANT # / TYPE 808673.9171 -4086658.5368 4115497.9775 APPROX POSITION XYZ 0.0000 0.0000 0.0000 ANTENNA: DELTA H/E/N 1 0 WAVELENGTH FACT L1/2 4 C1 L1 S1 # / TYPES OF OBSERV %s%13.7f GPS TIME OF FIRST OBS END OF HEADER """ % ( datetime.datetime.utcnow().strftime("%Y%m%d %H%M%S"), self.t.strftime(" %Y %m %d %H %M"), self.t.second + self.t.microsecond * 1e-6, ) self.rinex_file.write(header) self.rinex_file.flush() hdr_fmt = "<dH" hdr_size = struct.calcsize(hdr_fmt) tow, wn = struct.unpack("<dH", data[:hdr_size]) self.gps_tow = tow self.gps_week = wn self.t = datetime.datetime(1980, 1, 5) + \ datetime.timedelta(weeks=self.gps_week) + \ datetime.timedelta(seconds=self.gps_tow) obs_fmt = '<ddfB' """ double P; /**< Pseudorange (m) */ double L; /**< Carrier-phase (cycles) */ float snr; /**< Signal-to-Noise ratio */ u8 prn; /**< Satellite number. */ """ obs_size = struct.calcsize(obs_fmt) self.n_obs = (len(data) - hdr_size) / obs_size obs_data = data[hdr_size:] self.obs = {} for i in range(self.n_obs): P, L, snr, prn = struct.unpack(obs_fmt, obs_data[:obs_size]) obs_data = obs_data[obs_size:] self.obs[prn] = (P, L, snr) if self.recording: prns = list(self.obs.iterkeys()) self.rinex_file.write("%s %10.7f 0 %2d" % (self.t.strftime(" %y %m %d %H %M"), self.t.second + self.t.microsecond*1e-6, len(prns))) while len(prns) > 0: prns_ = prns[:12] prns = prns[12:] for prn in prns_: self.rinex_file.write('G%2d' % (prn+1)) self.rinex_file.write(' ' * (12 - len(prns_))) self.rinex_file.write('\n') for prn in list(self.obs.iterkeys()): # G 3 C1C L1C D1C self.rinex_file.write("%14.3f " % self.obs[prn][0]) self.rinex_file.write("%14.3f " % self.obs[prn][1]) self.rinex_file.write("%14.3f \n" % self.obs[prn][2]) self.rinex_file.flush() self.update_obs() def __init__(self, link, name='Rover', relay=False): super(ObservationView, self).__init__() self.obs_count = 0 self.n_obs = 1 self.relay = relay self.name = name self.rinex_file = None self.link = link self.link.add_callback(ids.NEW_OBS, self.obs_callback) self.python_console_cmds = { 'obs': self }
class TraitBrowser(HasTraits): """ View value heirarchy in traits. Selection not working at the moment, so use is limitd. Uses monkeypatching through hackedvtree.py to change output style. """ traits_tree = Any #<-- Instance of stack use_default = Bool(False) monkeyfunction = Function # For display/view only _allowed = Str #<-- defined in subclasses _infostr = Property(Str, depends_on='use_default') def _get__infostr(self): """ View message. """ if not self.use_default: return 'Showing %s Traits only' % self._allowed return 'Showing Everything' traits_view = View(HGroup( Item('use_default', label='Show All Traits'), Item('_infostr', style='readonly', show_label=False)), Item('traits_tree', editor=ValueEditor(), show_label=False), title='Trait Browser', buttons=OKCancelButtons, resizable=True, width=.4, height=.4) def __init__(self, *args, **kwargs): super(TraitBrowser, self).__init__(*args, **kwargs) TraitsNode.tno_get_children = self.monkeyfunction #def _hide_changed(self): #""" Hide or show various attributes in ValueEditor. Constructs #a partial function based on values of self.hide and monkeypatches #TraitNode. Very hacky. #""" #if self.use_default: #return #hide = self.hide #if 'private' in hide: #hide_privates = True #else: #hide_privates = False ## Could make this more "general" with a mapper, but it's 1am #allowed = [] #if 'strings' not in hide: #allowed.append(basestring) #if 'scalars' not in hide: #allowed += [int, float] #if 'arrays' not in hide: #allowed.append(np.ndarray) # X MONKEY PATCH # http://stackoverflow.com/questions/28185336/monkey-patching-with-a-partial-function #outfcn = functools.partial(hackednode, #hide_privates = hide_privates, #allowed = allowed) #TraitsNode.tno_get_children = outfcn ##XXX ## SINCE MONKEY PATCHING DOES WORK, JUST GOING TO CALL THIS WITH DEFAULTS #TraitsNode.tno_get_children = self.monkeyfunction ##Force Refresh view #self.trait_view().updated = True def _use_default_changed(self): """ Restore ValueEditor to unchanged values. """ if self.use_default: _RESTORE() else: TraitsNode.tno_get_children = self.monkeyfunction # Force Refresh view self.trait_view().updated = True
class UItem(Item): """ An Item that has no label. """ show_label = Bool(False)
class DataSourceFactory(HasStrictTraits): """ Factory for creating data sources. The information about the organisation of the data is given by setting the public traits. """ # Whether the position is implicitly inferred from the array indices position_implicit = Bool(False) # Whether the data is on an orthogonal grid orthogonal_grid = Bool(False) # If the data is unstructured unstructured = Bool(False) # If the factory should attempt to connect the data points connected = Bool(True) # The position of the data points position_x = ArrayOrNone position_y = ArrayOrNone position_z = ArrayOrNone # Connectivity array. If none, it is implicitly inferred from the array # indices connectivity_triangles = ArrayOrNone # Whether or not the data points should be connected. lines = Bool(False) # The scalar data array scalar_data = ArrayOrNone # Whether there is vector data has_vector_data = Bool(False) # The vector components vector_u = ArrayOrNone vector_v = ArrayOrNone vector_w = ArrayOrNone #---------------------------------------------------------------------- # Private traits #---------------------------------------------------------------------- _vtk_source = Instance(tvtk.DataSet) _mayavi_source = Instance(Source) #---------------------------------------------------------------------- # Private interface #---------------------------------------------------------------------- def _add_scalar_data(self): """ Adds the scalar data to the vtk source. """ if self.scalar_data is not None: scalars = self.scalar_data.ravel() self._vtk_source.point_data.scalars = scalars def _add_vector_data(self): """ Adds the vector data to the vtk source. """ if self.has_vector_data: vectors = c_[self.vector_u.ravel(), self.vector_v.ravel(), self.vector_w.ravel(), ] self._vtk_source.point_data.vectors = vectors def _mk_polydata(self): """ Creates a PolyData vtk data set using the factory's attributes. """ points = c_[self.position_x.ravel(), self.position_y.ravel(), self.position_z.ravel(), ] lines = None if self.lines: np = len(points) - 1 lines = zeros((np, 2), 'l') lines[:, 0] = arange(0, np - 0.5, 1, 'l') lines[:, 1] = arange(1, np + 0.5, 1, 'l') self._vtk_source = tvtk.PolyData(points=points, lines=lines) if (self.connectivity_triangles is not None and self.connected): assert self.connectivity_triangles.shape[1] == 3, \ "The connectivity list must be Nx3." self._vtk_source.polys = self.connectivity_triangles self._mayavi_source = VTKDataSource(data=self._vtk_source) def _mk_image_data(self): """ Creates an ImageData VTK data set and the associated ArraySource using the factory's attributes. """ self._mayavi_source = ArraySource(transpose_input_array=True, scalar_data=self.scalar_data, origin=[0., 0., 0], spacing=[1, 1, 1]) self._vtk_source = self._mayavi_source.image_data def _mk_rectilinear_grid(self): """ Creates a RectilinearGrid VTK data set using the factory's attributes. """ rg = tvtk.RectilinearGrid() x = self.position_x.squeeze() if x.ndim == 3: x = x[:, 0, 0] y = self.position_y.squeeze() if y.ndim == 3: y = y[0, :, 0] z = self.position_z.squeeze() if z.ndim == 3: z = z[0, 0, :] # FIXME: We should check array size here. rg.dimensions = (x.size, y.size, z.size) rg.x_coordinates = x rg.y_coordinates = y rg.z_coordinates = z self._vtk_source = rg self._mayavi_source = VTKDataSource(data=self._vtk_source) def _mk_structured_grid(self): """ Creates a StructuredGrid VTK data set using the factory's attributes. """ # FIXME: We need to figure out the dimensions of the data # here, if any. sg = tvtk.StructuredGrid(dimensions=self.scalar_data.shape) sg.points = c_[self.position_x.ravel(), self.position_y.ravel(), self.position_z.ravel(), ] self._vtk_source = sg self._mayavi_source = VTKDataSource(data=self._vtk_source) #---------------------------------------------------------------------- # Public interface #---------------------------------------------------------------------- def build_data_source(self, **traits): """ Uses all the information given by the user on his data structure to figure out the right data structure. """ self.trait_set(**traits) if not self.lines: if self.position_implicit: self._mk_image_data() elif self.orthogonal_grid: self._mk_rectilinear_grid() elif self.connectivity_triangles is None: if self.unstructured: self._mk_polydata() else: self._mk_structured_grid() else: self._mk_polydata() else: self._mk_polydata() self._add_scalar_data() self._add_vector_data() return self._mayavi_source
class UCustom(Custom): """ An Item using a 'custom' style with no label. """ show_label = Bool(False)
class AbstractDataExporter(ABCHasStrictTraits): """ ABC for classes that export data from a data view. Concrete classes should implement the ``get_data`` method so that it produces a value that can be serialized using the provided ``format``. Some convenience methods are provided to get text values, as that is a common use-case. """ #: The DataFormat used to serialize the exported data. format = Instance(DataFormat) #: Whether to get item data from the text channel, if available. is_text = Bool() def add_data(self, data_wrapper, model, indices): """ Add data to the data wrapper from the model and indices. Parameters ---------- data_wrapper : DataWrapper instance The data wrapper that will be used to export data. model : AbstractDataModel instance The data model holding the data. indices : list of (row, column) index pairs The indices where the data is to be stored. """ try: data = self.get_data(model, indices) data_wrapper.set_format(self.format, data) except DataViewGetError: pass @abstractmethod def get_data(self, model, indices): """ Get the data to be exported from the model and indices. Parameters ---------- model : AbstractDataModel instance The data model holding the data. indices : list of (row, column) index pairs The indices where the data is to be stored. Returns ------- data : any The data, of a type that can be serialized by the format. """ raise NotImplementedError() def get_value(self, model, row, column): """ Utility method to extract a value at a given index. If ``is_text`` is True, it will use the ``get_text()`` method to extract the value, otherwise it will try to use the editor value if it exists, and failing that the raw value returned from the model. """ value_type = model.get_value_type(row, column) if self.is_text: if value_type.has_text(model, row, column): value = value_type.get_text(model, row, column) else: value = "" elif value_type.has_editor_value(model, row, column): value = value_type.get_editor_value(model, row, column) else: value = model.get_value(row, column) return value def _is_text_default(self): return self.format.mimetype.startswith('text/')
class UReadonly(Readonly): """ An Item using a 'readonly' style with no label. """ show_label = Bool(False)
class DataFrameEditor(BasicEditorFactory): """ Editor factory for basic data frame editor """ #: The editor implementation class. klass = Property() #: Should an index column be displayed. show_index = Bool(True) #: Should column headers be displayed. show_titles = Bool(True) #: Optional list of either column ID or pairs of (column title, column ID). columns = List(Either(Str, Tuple(Str, Str))) #: The format for each element, or a mapping column ID to format. formats = Either(Str, Dict, default="%s") #: The font for each element, or a mapping column ID to font. fonts = Either(Font, Dict, default="Courier 10") #: The optional extended name of the trait to synchronize the selection #: values with: selected = Str() #: The optional extended name of the trait to synchronize the selection rows #: with: selected_row = Str() #: Whether or not to allow selection. selectable = Bool(True) #: Whether or not to allow for multiple selections multi_select = Bool(False) #: The optional extended name of the trait to synchronize the activated #: value with: activated = Str() #: The optional extended name of the trait to synchronize the activated #: value's row with: activated_row = Str() #: The optional extended name of the trait to synchronize left click data #: with. The data is a TabularEditorEvent: clicked = Str() #: The optional extended name of the trait to synchronize left double click #: data with. The data is a TabularEditorEvent: dclicked = Str() #: The optional extended name of the Event trait that should be used to #: trigger a scroll-to command. The data is an integer giving the row. scroll_to_row = Str() #: Controls behavior of scroll to row scroll_to_row_hint = Enum("center", "top", "bottom", "visible") #: The optional extended name of the Event trait that should be used to #: trigger a scroll-to command. The data is an integer giving the column. scroll_to_column = Str() #: The optional extended name of the trait to synchronize right click data #: with. The data is a TabularEditorEvent: right_clicked = Str() #: The optional extended name of the trait to synchronize right double #: clicked data with. The data is a TabularEditorEvent: right_dclicked = Str() #: The optional extended name of the trait to synchronize column #: clicked data with. The data is a TabularEditorEvent: column_clicked = Str() #: The optional extended name of the trait to synchronize column #: right clicked data with. The data is a TabularEditorEvent: column_right_clicked = Str() #: Whether or not the entries can be edited. editable = Bool(False) #: What type of operations are allowed on the list: operations = List( Enum("delete", "insert", "append", "edit", "move"), ["delete", "insert", "append", "edit", "move"], ) #: The optional extended name of the trait used to indicate that a complete #: table update is needed: update = Str() #: The optional extended name of the trait used to indicate that the table #: just needs to be repainted. refresh = Str() #: Set to override the default dataframe adapter adapter = Instance(DataFrameAdapter) def _get_klass(self): """ The class used to construct editor objects. """ return toolkit_object("data_frame_editor:_DataFrameEditor")
class Editor(HasPrivateTraits): """ Represents an editing control for an object trait in a Traits-based user interface. """ #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The UI (user interface) this editor is part of: ui = Instance('traitsui.ui.UI') # 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) # 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 # Text description of the object trait being edited: description = ReadOnly # The Item object used to create this editor: item = Instance(Item, ()) # The GUI widget defined by this editor: control = Any # The GUI label (if any) defined by this editor: label_control = Any # 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 = factory_trait # 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) #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__(self, parent, **traits): """ Initializes the editor object. """ HasPrivateTraits.__init__(self, **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') #--------------------------------------------------------------------------- # Finishes editor set-up: #--------------------------------------------------------------------------- def prepare(self, parent): """ Finishes setting up the editor. """ 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() #--------------------------------------------------------------------------- # Finishes initializing the editor by creating the underlying toolkit # widget: #--------------------------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ raise NotImplementedError #--------------------------------------------------------------------------- # Assigns focus to the editor's underlying toolkit widget: #--------------------------------------------------------------------------- def set_focus(self): """ Assigns focus to the editor's underlying toolkit widget. """ raise NotImplementedError #--------------------------------------------------------------------------- # Disposes of the contents of an editor: #--------------------------------------------------------------------------- def dispose(self): """ Disposes of the contents of an editor. """ 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) if self._user_from is not None: for name, handler in self._user_from: self.on_trait_change(handler, name, remove=True) if self._user_to is not None: for object, name, handler in self._user_to: object.on_trait_change(handler, name, remove=True) # Break linkages to references we no longer need: self.object = self.ui = self.item = self.factory = self.control = \ self.label_control = self.old_value = self._context_object = None #--------------------------------------------------------------------------- # Returns the context object the editor is using (Property implementation): #--------------------------------------------------------------------------- @cached_property def _get_context_object(self): """ Returns the context object the editor is using (Property implementation). """ 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 #--------------------------------------------------------------------------- # Returns the extended trait name being edited (Property implementation): #--------------------------------------------------------------------------- @cached_property def _get_extended_name(self): """ Returns the extended trait name being edited (Property implementation). """ return ('%s.%s' % (self.object_name, self.name)).split('.', 1)[1] #--------------------------------------------------------------------------- # Returns the trait the editor is editing (Property implementation): #--------------------------------------------------------------------------- def _get_value_trait(self): """ Returns the trait the editor is editing (Property implementation). """ return self.object.trait(self.name) #--------------------------------------------------------------------------- # Gets/Sets the associated object trait's value: #--------------------------------------------------------------------------- def _get_value(self): return getattr(self.object, self.name, Undefined) def _set_value(self, value): if self.ui and self.name != 'None': self.ui.do_undoable(self.__set_value, value) def __set_value(self, value): self._no_update = True try: 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 finally: self._no_update = False #--------------------------------------------------------------------------- # Returns the text representation of a specified object trait value: #--------------------------------------------------------------------------- def string_value(self, value, format_func=None): """ Returns the text representation of a specified object trait value. If the **format_func** attribute is set on the editor factory, then this method calls that function to do the formatting. If the **format_str** attribute is set on the editor factory, then this method uses that string for formatting. If neither attribute is set, then this method just calls the built-in unicode() function. """ factory = self.factory #print "editor", value.decode(encoding='UTF-16',errors='strict') if factory.format_func is not None: return factory.format_func(value) if factory.format_str != '': return factory.format_str % value if format_func is not None: return format_func(value) """if value.startswith(b'\xFF\xD8')and value.endswith(b'\xFF\xD9'): from base64 import b64encode mime='image/jpeg' fmt = 'data:{mime};base64,{data}' b64 = b64encode(value).decode('ascii') return fmt.format(mime=mime, data=b64)""" return unicode(value) #--------------------------------------------------------------------------- # Returns the text representation of the object trait: #--------------------------------------------------------------------------- def _get_str_value(self): """ Returns the text representation of the object trait. """ return self.string_value(getattr(self.object, self.name, Undefined)) #--------------------------------------------------------------------------- # Returns the text representation of a specified value: #--------------------------------------------------------------------------- def _str(self, value): """ Returns the text representation of a specified value. """ # In Unicode! return unicode(value) #--------------------------------------------------------------------------- # Handles an error that occurs while setting the object's trait value: # # (Should normally be overridden in a subclass) #--------------------------------------------------------------------------- def error(self, excp): """ Handles an error that occurs while setting the object's trait value. """ pass #--------------------------------------------------------------------------- # Performs updates when the object trait changes: #--------------------------------------------------------------------------- def _update_editor(self, object, name, old_value, new_value): """ Performs updates when the object trait changes. """ # 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 = eval(self.object_name, globals(), self.ui.context) # 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 it is not for an event): if object.base_trait(name).type != 'event': 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._no_update: # Update the editor control to reflect the current object state: self.update_editor() #--------------------------------------------------------------------------- # Logs a change made in the editor: #--------------------------------------------------------------------------- def log_change(self, undo_factory, *undo_args): """ Logs a change made in the editor. """ # Indicate that the contents of the user interface have been changed: ui = self.ui ui.modified = True # 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) #--------------------------------------------------------------------------- # Updates the editor when the object trait changes external to the editor: # # (Should normally be overridden in a subclass) #--------------------------------------------------------------------------- def update_editor(self): """ Updates the editor when the object trait changes externally to the editor. """ pass #--------------------------------------------------------------------------- # Creates an undo history entry: # # (Can be overridden in a subclass for special value types) #--------------------------------------------------------------------------- def get_undo_item(self, object, name, old_value, new_value): """ Creates an undo history entry. """ return UndoItem(object=object, name=name, old_value=old_value, new_value=new_value) #--------------------------------------------------------------------------- # Returns a tuple of the form ( context_object, name[.name...], callable ) # for a specified extended name of the form: name or # context_object_name.name[.name...]: #--------------------------------------------------------------------------- def parse_extended_name(self, name): """ Returns a tuple of the form ( context_object, 'name[.name...], callable ) for a specified extended name of the form: 'name' or 'context_object_name.name[.name...]'. """ col = name.find('.') if col < 0: object = self.context_object else: object, name = self.ui.context[name[:col]], name[col + 1:] return (object, name, eval("lambda obj=object: obj." + name)) #--------------------------------------------------------------------------- # Initializes and synchronizes (as needed) editor traits with the value of # corresponding factory traits: #--------------------------------------------------------------------------- def _sync_values(self): """ Initializes and synchronizes (as needed) editor traits with the value of corresponding factory traits. """ factory = self.factory for name, trait in factory.traits(sync_value=not_none): value = getattr(factory, name) if isinstance(value, ContextValue): self_trait = self.trait(name) self.sync_value(value.name, name, self_trait.sync_value or trait.sync_value, self_trait.is_list is True) elif value is not Undefined: setattr(self, name, value) #--------------------------------------------------------------------------- # Sets/Unsets synchronization between an editor trait and a user object # trait: #--------------------------------------------------------------------------- def sync_value(self, user_name, editor_name, mode='both', is_list=False): """ Sets or unsets synchronization between an editor trait and a user object trait. """ if user_name != '': key = '%s:%s' % (user_name, editor_name) if self._no_trait_update is None: self._no_trait_update = {} user_ref = 'user_object' col = user_name.find('.') if col < 0: user_object = self.context_object xuser_name = user_name else: user_object = self.ui.context[user_name[:col]] user_name = xuser_name = user_name[col + 1:] col = user_name.rfind('.') if col >= 0: user_ref += ('.' + user_name[:col]) user_name = user_name[col + 1:] user_value = compile('%s.%s' % (user_ref, user_name), '<string>', 'eval') user_ref = compile(user_ref, '<string>', 'eval') if mode in ('from', 'both'): def user_trait_modified(new): # Need this to include 'user_object' in closure: user_object if key not in self._no_trait_update: self._no_trait_update[key] = None try: setattr(self, editor_name, new) except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] user_object.on_trait_change(user_trait_modified, xuser_name) if self._user_to is None: self._user_to = [] self._user_to.append( (user_object, xuser_name, user_trait_modified)) if is_list: def user_list_modified(event): if isinstance(event, TraitListEvent): if key not in self._no_trait_update: self._no_trait_update[key] = None n = event.index try: getattr(self, editor_name )[n:n + len(event.removed)] = event.added except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] user_object.on_trait_change(user_list_modified, xuser_name + '_items') self._user_to.append((user_object, xuser_name + '_items', user_list_modified)) try: setattr(self, editor_name, eval(user_value)) except: from traitsui.api import raise_to_debug raise_to_debug() if mode in ('to', 'both'): def editor_trait_modified(new): # Need this to include 'user_object' in closure: user_object if key not in self._no_trait_update: self._no_trait_update[key] = None try: setattr(eval(user_ref), user_name, new) except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] self.on_trait_change(editor_trait_modified, editor_name) if self._user_from is None: self._user_from = [] self._user_from.append((editor_name, editor_trait_modified)) if is_list: def editor_list_modified(event): # Need this to include 'user_object' in closure: user_object if key not in self._no_trait_update: self._no_trait_update[key] = None n = event.index try: eval(user_value )[n:n + len(event.removed)] = event.added except: from traitsui.api import raise_to_debug raise_to_debug() del self._no_trait_update[key] self.on_trait_change(editor_list_modified, editor_name + '_items') self._user_from.append( (editor_name + '_items', editor_list_modified)) if mode == 'to': try: setattr(eval(user_ref), user_name, getattr(self, editor_name)) except: from traitsui.api import raise_to_debug raise_to_debug() #-- UI preference save/restore interface ----------------------------------- #--------------------------------------------------------------------------- # Restores any saved user preference information associated with the # editor: #--------------------------------------------------------------------------- def restore_prefs(self, prefs): """ Restores any saved user preference information associated with the editor. """ pass #--------------------------------------------------------------------------- # Returns any user preference information associated with the editor: #--------------------------------------------------------------------------- def save_prefs(self): """ Returns any user preference information associated with the editor. """ return None
class TextGrid(Component): """ A 2D grid of string values """ # A 2D array of strings string_array = Array # The cell size can be set to a tuple (w,h) or to "auto". cell_size = Property #------------------------------------------------------------------------ # Appereance traits #------------------------------------------------------------------------ # The font to use for the text of the grid font = KivaFont("modern 14") # The color of the text text_color = black_color_trait # The padding around each cell cell_padding = Int(5) # The thickness of the border between cells cell_border_width = Int(1) # The color of the border between cells cell_border_color = black_color_trait # The dash style of the border between cells cell_border_style = LineStyle("solid") # Text color of highlighted items highlight_color = ColorTrait("red") # Cell background color of highlighted items highlight_bgcolor = ColorTrait("lightgray") # A list of tuples of the (i,j) of selected cells selected_cells = List #------------------------------------------------------------------------ # Private traits #------------------------------------------------------------------------ # Are our cached extent values still valid? _cache_valid = Bool(False) # The maximum width and height of all cells, as a tuple (w,h) _cached_cell_size = Tuple # The maximum (leading, descent) of all the text strings (positive value) _text_offset = Array # An array NxMx2 of the x,y positions of the lower-left coordinates of # each cell _cached_cell_coords = Array # "auto" or a tuple _cell_size = Trait("auto", Any) #------------------------------------------------------------------------ # Public methods #------------------------------------------------------------------------ def __init__(self, **kwtraits): super(Component, self).__init__(**kwtraits) self.selected_cells = [] return #------------------------------------------------------------------------ # AbstractComponent interface #------------------------------------------------------------------------ def _draw_mainlayer(self, gc, view_bounds=None, mode="default"): text_color = self.text_color_ highlight_color = self.highlight_color_ highlight_bgcolor = self.highlight_bgcolor_ padding = self.cell_padding border_width = self.cell_border_width with gc: gc.set_stroke_color(text_color) gc.set_fill_color(text_color) gc.set_font(self.font) gc.set_text_position(0, 0) width, height = self._get_actual_cell_size() numrows, numcols = self.string_array.shape # draw selected backgrounds # XXX should this be in the background layer? for j, row in enumerate(self.string_array): for i, text in enumerate(row): if (i, j) in self.selected_cells: gc.set_fill_color(highlight_bgcolor) ll_x, ll_y = self._cached_cell_coords[i, j + 1] # render this a bit big, but covered by border gc.rect(ll_x, ll_y, width + 2 * padding + border_width, height + 2 * padding + border_width) gc.fill_path() gc.set_fill_color(text_color) self._draw_grid_lines(gc) for j, row in enumerate(self.string_array): for i, text in enumerate(row): x,y = self._cached_cell_coords[i,j+1] + self._text_offset + \ padding + border_width/2.0 if (i, j) in self.selected_cells: gc.set_fill_color(highlight_color) gc.set_stroke_color(highlight_color) gc.set_text_position(x, y) gc.show_text(text) gc.set_stroke_color(text_color) gc.set_fill_color(text_color) else: gc.set_text_position(x, y) gc.show_text(text) return #------------------------------------------------------------------------ # Private methods #------------------------------------------------------------------------ def _draw_grid_lines(self, gc): gc.set_stroke_color(self.cell_border_color_) gc.set_line_dash(self.cell_border_style_) gc.set_line_width(self.cell_border_width) # Skip the leftmost and bottommost cell coords (since Y axis is reversed, # the bottommost coord is the last one) x_points = self._cached_cell_coords[:, 0, 0] y_points = self._cached_cell_coords[0, :, 1] for x in x_points: gc.move_to(x, self.y) gc.line_to(x, self.y + self.height) gc.stroke_path() for y in y_points: gc.move_to(self.x, y) gc.line_to(self.x + self.width, y) gc.stroke_path() return def _compute_cell_sizes(self): if not self._cache_valid: gc = font_metrics_provider() max_w = 0 max_h = 0 min_l = 0 min_d = 0 for text in self.string_array.ravel(): gc.set_font(self.font) l, d, w, h = gc.get_text_extent(text) if -l + w > max_w: max_w = -l + w if -d + h > max_h: max_h = -d + h if l < min_l: min_l = l if d < min_d: min_d = d self._cached_cell_size = (max_w, max_h) self._text_offset = array([-min_l, -min_d]) self._cache_valid = True return def _compute_positions(self): if self.string_array is None or len(self.string_array.shape) != 2: return width, height = self._get_actual_cell_size() numrows, numcols = self.string_array.shape cell_width = width + 2 * self.cell_padding + self.cell_border_width cell_height = height + 2 * self.cell_padding + self.cell_border_width x_points = arange( numcols + 1) * cell_width + self.cell_border_width / 2.0 + self.x y_points = arange( numrows + 1) * cell_height + self.cell_border_width / 2.0 + self.y tmp = dstack( (repeat(x_points[:, newaxis], numrows + 1, axis=1), repeat(y_points[:, newaxis].T, numcols + 1, axis=0))) # We have to reverse the y-axis (e.g. the 0th row needs to be at the # highest y-position). self._cached_cell_coords = tmp[:, ::-1] return def _update_bounds(self): if self.string_array is not None and len(self.string_array.shape) == 2: rows, cols = self.string_array.shape margin = 2 * self.cell_padding + self.cell_border_width width, height = self._get_actual_cell_size() self.bounds = [ cols * (width + margin) + self.cell_border_width, rows * (height + margin) + self.cell_border_width ] else: self.bounds = [0, 0] def _get_actual_cell_size(self): if self._cell_size == "auto": if not self._cache_valid: self._compute_cell_sizes() return self._cached_cell_size else: if not self._cache_valid: # actually computing the text offset self._compute_cell_sizes() return self._cell_size #------------------------------------------------------------------------ # Event handlers #------------------------------------------------------------------------ def normal_left_down(self, event): self.selected_cells = [self._get_index_for_xy(event.x, event.y)] self.request_redraw() def _get_index_for_xy(self, x, y): width, height = array(self._get_actual_cell_size()) + 2*self.cell_padding \ + self.cell_border_width numrows, numcols = self.string_array.shape i = int((x - self.padding_left) / width) j = numrows - (int((y - self.padding_bottom) / height) + 1) shape = self.string_array.shape if 0 <= i < shape[1] and 0 <= j < shape[0]: return i, j else: return None #------------------------------------------------------------------------ # Trait events, property setters and getters #------------------------------------------------------------------------ def _string_array_changed(self, old, new): if self._cell_size == "auto": self._cache_valid = False self._compute_cell_sizes() self._compute_positions() self._update_bounds() @on_trait_change('cell_border_width,cell_padding') def cell_properties_changed(self): self._compute_positions() self._update_bounds() def _set_cell_size(self, newsize): self._cell_size = newsize if newsize == "auto": self._compute_cell_sizes() self._compute_positions() self._update_bounds() def _get_cell_size(self): return self._cell_size
class Recorder(HasTraits, ICameraListener): """Container to save all records of a experiment""" #========================================================================= # Important components to interact #========================================================================= plotview = Instance(PlotView, ()) #========================================================================= # Properties of the recorder #========================================================================= input_ports = List(Port) container = List(List) record_information = List() images = List() record_mode = Bool() logger = getLogger('Application') def __init__(self, record_mode): self.logger.debug('Initialize Recorder') self.record_mode = record_mode self.plotview.model = self self.result_viewer = ResultViewer(self) def append(self, info, rec): """Appends new values to the recorder. :param info: Values of the input ports. :type info: List(Float) :param rec: Recording time. :type rec: str. """ pid, value = info for i in range(self.n): p = self.input_ports[i] if p.id == pid: self.container[i].append((rec, value)) def reset(self, input_ports): """Reset all information of the recorder :param input_ports: All input ports of the measuring card :type input_ports: List(Port) """ del self.input_ports[:] del self.container[:] del self.record_information[:] self.plotview.update_subplots(input_ports) self.n = len(input_ports) for i in range(self.n): self.input_ports.append(input_ports[i]) self.container.append([]) def get_values(self): """Returns all recorded values""" res = [] for i in range(len(self.container[0])): row = [] for j in range(self.n): rec, val = self.container[j][i] row.append(val) row.insert(0, time_to_secs(rec)) res.append(row) return array(res) #========================================================================= # Update methods #========================================================================= def _update_record_information(self, index): """Update the record information by the given index :param index: Index of the values which should show :type index: int """ self.index = index del self.record_information[:] for i in range(self.n): rec, val = self.container[i][index] label = '{0}: {1:.3f}'.format(self.input_ports[i].name, val) self.record_information.append(label) self.record_information.append('Time: {0}'.format(rec)) if not self.show_reference: files = get_all_files(images_dir) self.update_reference(files[0]) self.plotview.update_focus_point(index) self.result_viewer.update_image(index) def update_reference(self, f): """Load the reference image from the reference dir""" self.logger.debug("Update reference image [Recorder]") self.reference_image = ImageResource(join(resized_images_dir, f)) self.show_reference = True def _update_plots(self): # Update all values with the currently values of the recorder. self.plotview.update_values(self.get_values()) def update_image(self, index): self.result_viewer.show_recorded_image(index) #========================================================================= # Methods to save and load the data #========================================================================= def save(self): self.logger.debug("Save recorded values [Recorder]") Recorder.save_values(self.get_values()) Recorder.save_images(self.images) @staticmethod def save_values(values): save(join(recorder_dir, recording_file), values) @staticmethod def save_images(images): data = {} data["images"] = [] for img in images: data["images"].append(img) with open(join(recorder_dir, recorder_file), 'w') as f: dump(data, f, indent=2) def load(self, input_ports): self.logger.debug("Load recorded values [Recorder]") with open(join(recorder_dir, recorder_file), 'r') as f: data = jload(f) self.images = data["images"] values = load(join(recorder_dir, recording_file)) self.reset(input_ports) nv = len(values) nip = len(input_ports) self.plotview.update_subplots(input_ports) self.plotview.update_values(values) for i in range(nv): t = secs_to_time(values[i, 0]) for j in range(nip): self.container[j].append((t, values[i, j + 1])) self.result_viewer.show_images=False self._update_record_information(0) self.show_images=True #========================================================================= # Traitsview + Traitsevent #========================================================================= result_viewer = Instance(ResultViewer) scroll_able = Bool(True) show_images = Bool(False) show_reference = Bool(False) reference_image = Image() def _show_images_changed(self): self.result_viewer.show_images=self.show_images view = View( VGroup( HGroup( VGroup( InstanceUItem('plotview', style='custom', width=400), VGroup( UItem('record_information', editor=ListStrEditor(auto_add=False, editable=False)), label="Recorded values:", visible_when='show_images' ) ), VGroup( VGroup( UItem('reference_image', width=300, height=225), label='Reference Image:', visible_when='show_reference' ), VGroup( UItem('result_viewer', style='custom', visible_when='show_images'), ), layout='normal' ), ) ) )
class SolutionView(HasTraits): python_console_cmds = Dict() # we need to doubleup on Lists to store the psuedo absolutes separately # without rewriting everything """ logging_v : toggle logging for velocity files directory_name_v : location and name of velocity files logging_p : toggle logging for position files directory_name_p : location and name of velocity files """ plot_history_max = Int(1000) logging_v = Bool(False) directory_name_v = File logging_p = Bool(False) directory_name_p = File lats_psuedo_abs = List() lngs_psuedo_abs = List() alts_psuedo_abs = List() table = List() dops_table = List() pos_table = List() vel_table = List() rtk_pos_note = Str( "It is necessary to enter the \"Surveyed Position\" settings for the base station in order to view the RTK Positions in this tab." ) plot = Instance(Plot) plot_data = Instance(ArrayPlotData) # Store plots we care about for legend running = Bool(True) zoomall = Bool(False) position_centered = Bool(False) clear_button = SVGButton(label='', tooltip='Clear', filename=os.path.join(determine_path(), 'images', 'iconic', 'x.svg'), width=16, height=16) zoomall_button = SVGButton(label='', tooltip='Zoom All', toggle=True, filename=os.path.join(determine_path(), 'images', 'iconic', 'fullscreen.svg'), width=16, height=16) center_button = SVGButton(label='', tooltip='Center on Solution', toggle=True, filename=os.path.join(determine_path(), 'images', 'iconic', 'target.svg'), width=16, height=16) paused_button = SVGButton(label='', tooltip='Pause', toggle_tooltip='Run', toggle=True, filename=os.path.join(determine_path(), 'images', 'iconic', 'pause.svg'), toggle_filename=os.path.join( determine_path(), 'images', 'iconic', 'play.svg'), width=16, height=16) traits_view = View( HSplit( VGroup( Item('table', style='readonly', editor=TabularEditor(adapter=SimpleAdapter()), show_label=False, width=0.3), Item('rtk_pos_note', show_label=False, resizable=True, editor=MultilineTextEditor(TextEditor(multi_line=True)), style='readonly', width=0.3, height=-40), ), VGroup( HGroup( Item('paused_button', show_label=False), Item('clear_button', show_label=False), Item('zoomall_button', show_label=False), Item('center_button', show_label=False), ), Item('plot', show_label=False, editor=ComponentEditor(bgcolor=(0.8, 0.8, 0.8))), ))) def _zoomall_button_fired(self): self.zoomall = not self.zoomall def _center_button_fired(self): self.position_centered = not self.position_centered def _paused_button_fired(self): self.running = not self.running def _reset_remove_current(self): self.plot_data.set_data('cur_lat_spp', []) self.plot_data.set_data('cur_lng_spp', []) self.plot_data.set_data('cur_alt_spp', []) self.plot_data.set_data('cur_lat_dgnss', []) self.plot_data.set_data('cur_lng_dgnss', []) self.plot_data.set_data('cur_alt_dgnss', []) self.plot_data.set_data('cur_lat_float', []) self.plot_data.set_data('cur_lng_float', []) self.plot_data.set_data('cur_alt_float', []) self.plot_data.set_data('cur_lat_fixed', []) self.plot_data.set_data('cur_lng_fixed', []) self.plot_data.set_data('cur_alt_fixed', []) def _clear_button_fired(self): self.tows = np.empty(self.plot_history_max) self.lats = np.empty(self.plot_history_max) self.lngs = np.empty(self.plot_history_max) self.alts = np.empty(self.plot_history_max) self.modes = np.empty(self.plot_history_max) self.plot_data.set_data('lat_spp', []) self.plot_data.set_data('lng_spp', []) self.plot_data.set_data('alt_spp', []) self.plot_data.set_data('lat_dgnss', []) self.plot_data.set_data('lng_dgnss', []) self.plot_data.set_data('alt_dgnss', []) self.plot_data.set_data('lat_float', []) self.plot_data.set_data('lng_float', []) self.plot_data.set_data('alt_float', []) self.plot_data.set_data('lat_fixed', []) self.plot_data.set_data('lng_fixed', []) self.plot_data.set_data('alt_fixed', []) self._reset_remove_current() def _pos_llh_callback(self, sbp_msg, **metadata): # Updating an ArrayPlotData isn't thread safe (see chaco issue #9), so # actually perform the update in the UI thread. if self.running: GUI.invoke_later(self.pos_llh_callback, sbp_msg) def update_table(self): self._table_list = self.table_spp.items() def auto_survey(self): if self.counter < 1000: self.counter = self.counter + 1 self.latitude_list.append(self.last_soln.lat) self.longitude_list.append(self.last_soln.lon) self.altitude_list.append(self.last_soln.height) self.latitude_list = self.latitude_list[-1000:] self.longitude_list = self.longitude_list[-1000:] self.altitude_list = self.altitude_list[-1000:] self.latitude = (sum(self.latitude_list)) / self.counter self.altitude = (sum(self.altitude_list)) / self.counter self.longitude = (sum(self.longitude_list)) / self.counter def pos_llh_callback(self, sbp_msg, **metadata): if sbp_msg.msg_type == SBP_MSG_POS_LLH_DEP_A: soln = MsgPosLLHDepA(sbp_msg) else: soln = MsgPosLLH(sbp_msg) self.last_soln = soln self.last_pos_mode = get_mode(soln) pos_table = [] soln.h_accuracy *= 1e-3 soln.v_accuracy *= 1e-3 tow = soln.tow * 1e-3 if self.nsec is not None: tow += self.nsec * 1e-9 if self.week is not None: t = datetime.datetime(1980, 1, 6) + \ datetime.timedelta(weeks=self.week) + \ datetime.timedelta(seconds=tow) tstr = t.strftime('%Y-%m-%d %H:%M') secs = t.strftime('%S.%f') if (self.directory_name_p == ''): filepath_p = time.strftime("position_log_%Y%m%d-%H%M%S.csv") else: filepath_p = os.path.join( self.directory_name_p, time.strftime("position_log_%Y%m%d-%H%M%S.csv")) if self.logging_p == False: self.log_file = None if self.logging_p: if self.log_file is None: self.log_file = sopen(filepath_p, 'w') self.log_file.write( "time,latitude(degrees),longitude(degrees),altitude(meters)," "h_accuracy(meters),v_accuracy(meters),n_sats,flags\n") self.log_file.write( '%s,%.10f,%.10f,%.4f,%.4f,%.4f,%d,%d\n' % ("{0}:{1:06.6f}".format(tstr, float(secs)), soln.lat, soln.lon, soln.height, soln.h_accuracy, soln.v_accuracy, soln.n_sats, soln.flags)) self.log_file.flush() if self.last_pos_mode == 0: pos_table.append(('GPS Time', EMPTY_STR)) pos_table.append(('GPS Week', EMPTY_STR)) pos_table.append(('GPS TOW', EMPTY_STR)) pos_table.append(('Num. Signals', EMPTY_STR)) pos_table.append(('Lat', EMPTY_STR)) pos_table.append(('Lng', EMPTY_STR)) pos_table.append(('Height', EMPTY_STR)) pos_table.append(('h_accuracy', EMPTY_STR)) pos_table.append(('v_accuracy', EMPTY_STR)) else: self.last_stime_update = time.time() if self.week is not None: pos_table.append( ('GPS Time', "{0}:{1:06.3f}".format(tstr, float(secs)))) pos_table.append(('GPS Week', str(self.week))) pos_table.append(('GPS TOW', "{:.3f}".format(tow))) pos_table.append(('Num. Sats', soln.n_sats)) pos_table.append(('Lat', soln.lat)) pos_table.append(('Lng', soln.lon)) pos_table.append(('Height', soln.height)) pos_table.append(('h_accuracy', soln.h_accuracy)) pos_table.append(('v_accuracy', soln.v_accuracy)) pos_table.append(('Pos Flags', '0x%03x' % soln.flags)) pos_table.append(('Pos Fix Mode', mode_dict[self.last_pos_mode])) self.auto_survey() # setup_plot variables self.lats[1:] = self.lats[:-1] self.lngs[1:] = self.lngs[:-1] self.alts[1:] = self.alts[:-1] self.tows[1:] = self.tows[:-1] self.modes[1:] = self.modes[:-1] self.lats[0] = soln.lat self.lngs[0] = soln.lon self.alts[0] = soln.height self.tows[0] = soln.tow self.modes[0] = self.last_pos_mode self.lats = self.lats[-self.plot_history_max:] self.lngs = self.lngs[-self.plot_history_max:] self.alts = self.alts[-self.plot_history_max:] self.tows = self.tows[-self.plot_history_max:] self.modes = self.modes[-self.plot_history_max:] # SPP spp_indexer, dgnss_indexer, float_indexer, fixed_indexer = None, None, None, None if np.any(self.modes): spp_indexer = (self.modes == SPP_MODE) dgnss_indexer = (self.modes == DGNSS_MODE) float_indexer = (self.modes == FLOAT_MODE) fixed_indexer = (self.modes == FIXED_MODE) # make sure that there is at least one true in indexer before setting if any(spp_indexer): self.plot_data.set_data('lat_spp', self.lats[spp_indexer]) self.plot_data.set_data('lng_spp', self.lngs[spp_indexer]) self.plot_data.set_data('alt_spp', self.alts[spp_indexer]) if any(dgnss_indexer): self.plot_data.set_data('lat_dgnss', self.lats[dgnss_indexer]) self.plot_data.set_data('lng_dgnss', self.lngs[dgnss_indexer]) self.plot_data.set_data('alt_dgnss', self.alts[dgnss_indexer]) if any(float_indexer): self.plot_data.set_data('lat_float', self.lats[float_indexer]) self.plot_data.set_data('lng_float', self.lngs[float_indexer]) self.plot_data.set_data('alt_float', self.alts[float_indexer]) if any(fixed_indexer): self.plot_data.set_data('lat_fixed', self.lats[fixed_indexer]) self.plot_data.set_data('lng_fixed', self.lngs[fixed_indexer]) self.plot_data.set_data('alt_fixed', self.alts[fixed_indexer]) # update our "current solution" icon if self.last_pos_mode == SPP_MODE: self._reset_remove_current() self.plot_data.set_data('cur_lat_spp', [soln.lat]) self.plot_data.set_data('cur_lng_spp', [soln.lon]) elif self.last_pos_mode == DGNSS_MODE: self._reset_remove_current() self.plot_data.set_data('cur_lat_dgnss', [soln.lat]) self.plot_data.set_data('cur_lng_dgnss', [soln.lon]) elif self.last_pos_mode == FLOAT_MODE: self._reset_remove_current() self.plot_data.set_data('cur_lat_float', [soln.lat]) self.plot_data.set_data('cur_lng_float', [soln.lon]) elif self.last_pos_mode == FIXED_MODE: self._reset_remove_current() self.plot_data.set_data('cur_lat_fixed', [soln.lat]) self.plot_data.set_data('cur_lng_fixed', [soln.lon]) else: pass # set-up table variables self.pos_table = pos_table self.table = self.pos_table + self.vel_table + self.dops_table # TODO: figure out how to center the graph now that we have two separate messages # when we selectively send only SPP, the centering function won't work anymore if not self.zoomall and self.position_centered: d = (self.plot.index_range.high - self.plot.index_range.low) / 2. self.plot.index_range.set_bounds(soln.lon - d, soln.lon + d) d = (self.plot.value_range.high - self.plot.value_range.low) / 2. self.plot.value_range.set_bounds(soln.lat - d, soln.lat + d) if self.zoomall: plot_square_axes( self.plot, ('lng_spp', 'lng_dgnss', 'lng_float', 'lng_fixed'), ('lat_spp', 'lat_dgnss', 'lat_float', 'lat_fixed')) def dops_callback(self, sbp_msg, **metadata): flags = 0 if sbp_msg.msg_type == SBP_MSG_DOPS_DEP_A: dops = MsgDopsDepA(sbp_msg) flags = 1 else: dops = MsgDops(sbp_msg) flags = dops.flags if flags != 0: self.dops_table = [('PDOP', '%.1f' % (dops.pdop * 0.01)), ('GDOP', '%.1f' % (dops.gdop * 0.01)), ('TDOP', '%.1f' % (dops.tdop * 0.01)), ('HDOP', '%.1f' % (dops.hdop * 0.01)), ('VDOP', '%.1f' % (dops.vdop * 0.01))] else: self.dops_table = [('PDOP', EMPTY_STR), ('GDOP', EMPTY_STR), ('TDOP', EMPTY_STR), ('HDOP', EMPTY_STR), ('VDOP', EMPTY_STR)] self.dops_table.append(('DOPS Flags', '0x%03x' % flags)) self.table = self.pos_table + self.vel_table + self.dops_table def vel_ned_callback(self, sbp_msg, **metadata): flags = 0 if sbp_msg.msg_type == SBP_MSG_VEL_NED_DEP_A: vel_ned = MsgVelNEDDepA(sbp_msg) flags = 1 else: vel_ned = MsgVelNED(sbp_msg) flags = vel_ned.flags tow = vel_ned.tow * 1e-3 if self.nsec is not None: tow += self.nsec * 1e-9 if self.week is not None: t = datetime.datetime(1980, 1, 6) + \ datetime.timedelta(weeks=self.week) + \ datetime.timedelta(seconds=tow) tstr = t.strftime('%Y-%m-%d %H:%M') secs = t.strftime('%S.%f') if self.directory_name_v == '': filepath_v = time.strftime("velocity_log_%Y%m%d-%H%M%S.csv") else: filepath_v = os.path.join( self.directory_name_v, time.strftime("velocity_log_%Y%m%d-%H%M%S.csv")) if self.logging_v == False: self.vel_log_file = None if self.logging_v: if self.vel_log_file is None: self.vel_log_file = sopen(filepath_v, 'w') self.vel_log_file.write( 'time,north(m/s),east(m/s),down(m/s),speed(m/s),flags,num_signals\n' ) self.vel_log_file.write( '%s,%.6f,%.6f,%.6f,%.6f,%d,%d\n' % ("{0}:{1:06.6f}".format(tstr, float(secs)), vel_ned.n * 1e-3, vel_ned.e * 1e-3, vel_ned.d * 1e-3, math.sqrt(vel_ned.n * vel_ned.n + vel_ned.e * vel_ned.e) * 1e-3, flags, vel_ned.n_sats)) self.vel_log_file.flush() if flags != 0: self.vel_table = [ ('Vel. N', '% 8.4f' % (vel_ned.n * 1e-3)), ('Vel. E', '% 8.4f' % (vel_ned.e * 1e-3)), ('Vel. D', '% 8.4f' % (vel_ned.d * 1e-3)), ] else: self.vel_table = [ ('Vel. N', EMPTY_STR), ('Vel. E', EMPTY_STR), ('Vel. D', EMPTY_STR), ] self.vel_table.append(('Vel Flags', '0x%03x' % flags)) self.table = self.pos_table + self.vel_table + self.dops_table def gps_time_callback(self, sbp_msg, **metadata): if sbp_msg.msg_type == SBP_MSG_GPS_TIME_DEP_A: time_msg = MsgGPSTimeDepA(sbp_msg) flags = 1 elif sbp_msg.msg_type == SBP_MSG_GPS_TIME: time_msg = MsgGPSTime(sbp_msg) flags = time_msg.flags if flags != 0: self.week = time_msg.wn self.nsec = time_msg.ns def __init__(self, link, dirname=''): super(SolutionView, self).__init__() self.lats = np.zeros(self.plot_history_max) self.lngs = np.zeros(self.plot_history_max) self.alts = np.zeros(self.plot_history_max) self.tows = np.zeros(self.plot_history_max) self.modes = np.zeros(self.plot_history_max) self.log_file = None self.directory_name_v = dirname self.directory_name_p = dirname self.vel_log_file = None self.last_stime_update = 0 self.last_soln = None self.counter = 0 self.latitude_list = [] self.longitude_list = [] self.altitude_list = [] self.altitude = 0 self.longitude = 0 self.latitude = 0 self.last_pos_mode = 0 self.plot_data = ArrayPlotData(lat_spp=[], lng_spp=[], alt_spp=[], cur_lat_spp=[], cur_lng_spp=[], lat_dgnss=[], lng_dgnss=[], alt_dgnss=[], cur_lat_dgnss=[], cur_lng_dgnss=[], lat_float=[], lng_float=[], alt_float=[], cur_lat_float=[], cur_lng_float=[], lat_fixed=[], lng_fixed=[], alt_fixed=[], cur_lat_fixed=[], cur_lng_fixed=[]) self.plot = Plot(self.plot_data) # 1000 point buffer self.plot.plot(('lng_spp', 'lat_spp'), type='line', line_width=0.1, name='', color=color_dict[SPP_MODE]) self.plot.plot(('lng_spp', 'lat_spp'), type='scatter', name='', color=color_dict[SPP_MODE], marker='dot', line_width=0.0, marker_size=1.0) self.plot.plot(('lng_dgnss', 'lat_dgnss'), type='line', line_width=0.1, name='', color=color_dict[DGNSS_MODE]) self.plot.plot(('lng_dgnss', 'lat_dgnss'), type='scatter', name='', color=color_dict[DGNSS_MODE], marker='dot', line_width=0.0, marker_size=1.0) self.plot.plot(('lng_float', 'lat_float'), type='line', line_width=0.1, name='', color=color_dict[FLOAT_MODE]) self.plot.plot(('lng_float', 'lat_float'), type='scatter', name='', color=color_dict[FLOAT_MODE], marker='dot', line_width=0.0, marker_size=1.0) self.plot.plot(('lng_fixed', 'lat_fixed'), type='line', line_width=0.1, name='', color=color_dict[FIXED_MODE]) self.plot.plot(('lng_fixed', 'lat_fixed'), type='scatter', name='', color=color_dict[FIXED_MODE], marker='dot', line_width=0.0, marker_size=1.0) # current values spp = self.plot.plot(('cur_lng_spp', 'cur_lat_spp'), type='scatter', name=mode_dict[SPP_MODE], color=color_dict[SPP_MODE], marker='plus', line_width=1.5, marker_size=5.0) dgnss = self.plot.plot(('cur_lng_dgnss', 'cur_lat_dgnss'), type='scatter', name=mode_dict[DGNSS_MODE], color=color_dict[DGNSS_MODE], marker='plus', line_width=1.5, marker_size=5.0) rtkfloat = self.plot.plot(('cur_lng_float', 'cur_lat_float'), type='scatter', name=mode_dict[FLOAT_MODE], color=color_dict[FLOAT_MODE], marker='plus', line_width=1.5, marker_size=5.0) rtkfix = self.plot.plot(('cur_lng_fixed', 'cur_lat_fixed'), type='scatter', name=mode_dict[FIXED_MODE], color=color_dict[FIXED_MODE], marker='plus', line_width=1.5, marker_size=5.0) plot_labels = ['SPP', 'DGPS', "RTK float", "RTK fixed"] plots_legend = dict(zip(plot_labels, [spp, dgnss, rtkfloat, rtkfix])) self.plot.legend.plots = plots_legend self.plot.legend.labels = plot_labels # sets order self.plot.legend.visible = True self.plot.index_axis.tick_label_position = 'inside' self.plot.index_axis.tick_label_color = 'gray' self.plot.index_axis.tick_color = 'gray' self.plot.index_axis.title = 'Longitude (degrees)' self.plot.index_axis.title_spacing = 5 self.plot.value_axis.tick_label_position = 'inside' self.plot.value_axis.tick_label_color = 'gray' self.plot.value_axis.tick_color = 'gray' self.plot.value_axis.title = 'Latitude (degrees)' self.plot.value_axis.title_spacing = 5 self.plot.padding = (25, 25, 25, 25) self.plot.tools.append(PanTool(self.plot)) zt = ZoomTool(self.plot, zoom_factor=1.1, tool_mode="box", always_on=False) self.plot.overlays.append(zt) self.link = link self.link.add_callback(self._pos_llh_callback, [SBP_MSG_POS_LLH_DEP_A, SBP_MSG_POS_LLH]) self.link.add_callback(self.vel_ned_callback, [SBP_MSG_VEL_NED_DEP_A, SBP_MSG_VEL_NED]) self.link.add_callback(self.dops_callback, [SBP_MSG_DOPS_DEP_A, SBP_MSG_DOPS]) self.link.add_callback(self.gps_time_callback, [SBP_MSG_GPS_TIME_DEP_A, SBP_MSG_GPS_TIME]) self.week = None self.nsec = 0 self.python_console_cmds = { 'solution': self, }
class ThemedTextEditor ( EditorFactory ): """ Traits UI simple, single line text editor with a themed (i.e. image) background. """ #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The background theme image to display: theme = ATheme # Dictionary that maps user input to other values: mapping = Dict( Str, Any ) # Is user input set on every keystroke? auto_set = Bool( True ) # Is user input set when the Enter key is pressed? enter_set = Bool( False ) # Is user input unreadable? (e.g., for a password) password = Bool( False ) # Function to evaluate textual user input: evaluate = Any # The object trait containing the function used to evaluate user input: evaluate_name = Str #--------------------------------------------------------------------------- # 'Editor' factory methods: #--------------------------------------------------------------------------- def simple_editor ( self, ui, object, name, description, parent ): return _ThemedTextEditor( parent, factory = self, ui = ui, object = object, name = name, description = description ) def custom_editor ( self, ui, object, name, description, parent ): return _ThemedTextEditor( parent, factory = self, ui = ui, object = object, name = name, description = description ) def text_editor ( self, ui, object, name, description, parent ): return _ThemedTextEditor( parent, factory = self, ui = ui, object = object, name = name, description = description ) def readonly_editor ( self, ui, object, name, description, parent ): return _ReadonlyTextEditor( parent, factory = self, ui = ui, object = object, name = name, description = description )
class Item(ViewSubElement): """ An element in a Traits-based user interface. Magic: - Items are rendered as layout elements if :attr:`name` is set to special values: * name='', the item is rendered as a static label * name='_', the item is rendered as a separator * name=' ', the item is rendered as a 5 pixel spacer * name='23' (any number), the item is rendered as a spacer of the size specified (number of pixels) """ # FIXME: all the logic for the name = '', '_', ' ', '23' magic is in # _GroupPanel._add_items in qt/ui_panel.py, which is a very unlikely place # to look for it. Ideally, that logic should be in this class. #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # A unique identifier for the item. If not set, it defaults to the value # of **name**. id = Str # User interface label for the item in the GUI. If this attribute is not # set, the label is the value of **name** with slight modifications: # underscores are replaced by spaces, and the first letter is capitalized. # If an item's **name** is not specified, its label is displayed as # static text, without any editor widget. label = Str # Name of the trait the item is editing: name = Str # Style-sheet to apply to item / group (Qt only) style_sheet = Str # Help text describing the purpose of the item. The built-in help handler # displays this text in a pop-up window if the user clicks the widget's # label. View-level help displays the help text for all items in a view. # If this attribute is not set, the built-in help handler generates a # description based on the trait definition. help = Str # The HasTraits object whose trait attribute the item is editing: object = ContainerDelegate # Presentation style for the item: style = ContainerDelegate # Docking style for the item: dock = ContainerDelegate # Image to display on notebook tabs: image = ContainerDelegate # The theme to use for the item itself: item_theme = ContainerDelegate # The theme to use for the item's label: label_theme = ContainerDelegate # Category of elements dragged from view: export = ContainerDelegate # Should a label be displayed for the item? show_label = Delegate('container', 'show_labels') # Editor to use for the item: editor = ItemEditor # Additional editor traits to be set if default traits editor to be used: editor_args = Dict # Should the item use extra space along its Group's non-layout axis? If set to # True, the widget expands to fill any extra space that is available in the # display. If set to True for more than one item in the same View, any extra # space is divided between them. If set to False, the widget uses only # whatever space it is explicitly (or implicitly) assigned. The default # value of Undefined means that the use (or non-use) of extra space will be # determined by the editor associated with the item. resizable = Bool(Undefined) # Should the item use extra space along its Group's layout axis? For # example, it a vertical group, should an item expand vertically to use # any extra space available in the group? springy = Bool(False) # Should the item use any extra space along its Group's non-layout # orientation? For example, in a vertical group, should an item expand # horizontally to the full width of the group? If left to the default value # of Undefined, the decision will be left up to the associated item editor. full_size = Bool(Undefined) # Should the item's label use emphasized text? If the label is not shown, # this attribute is ignored. emphasized = Bool(False) # Should the item receive focus initially? has_focus = Bool(False) # Pre-condition for including the item in the display. If the expression # evaluates to False, the item is not defined in the display. Conditions # for **defined_when** are evaluated only once, when the display is first # constructed. Use this attribute for conditions based on attributes that # vary from object to object, but that do not change over time. For example, # displaying a 'maiden_name' item only for female employees in a company # database. defined_when = Str # Pre-condition for showing the item. If the expression evaluates to False, # the widget is not visible (and disappears if it was previously visible). # If the value evaluates to True, the widget becomes visible. All # **visible_when** conditions are checked each time that any trait value # is edited in the display. Therefore, you can use **visible_when** # conditions to hide or show widgets in response to user input. visible_when = Str # Pre-condition for enabling the item. If the expression evaluates to False, # the widget is disabled, that is, it does not accept input. All # **enabled_when** conditions are checked each time that any trait value # is edited in the display. Therefore, you can use **enabled_when** # conditions to enable or disable widgets in response to user input. enabled_when = Str # Amount of extra space, in pixels, to add around the item. Values must be # integers between -15 and 15. Use negative values to subtract from the # default spacing. padding = Padding # Tooltip to display over the item, when the mouse pointer is left idle # over the widget. Make this text as concise as possible; use the **help** # attribute to provide more detailed information. tooltip = Str # A Callable to use for formatting the contents of the item. This function # or method is called to create the string representation of the trait value # to be edited. If the widget does not use a string representation, this # attribute is ignored. format_func = Callable # Python format string to use for formatting the contents of the item. # The format string is applied to the string representation of the trait # value before it is displayed in the widget. This attribute is ignored if # the widget does not use a string representation, or if the # **format_func** is set. format_str = Str # Requested width of the editor (in pixels or fraction of available width). # For pixel values (i.e. values not in the range from 0.0 to 1.0), the # actual displayed width is at least the maximum of **width** and the # optimal width of the widget as calculated by the GUI toolkit. Specify a # negative value to ignore the toolkit's optimal width. For example, use # -50 to force a width of 50 pixels. The default value of -1 ensures that # the toolkit's optimal width is used. # # A value in the range from 0.0 to 1.0 specifies the fraction of the # available width to assign to the editor. Note that the value is not an # absolute value, but is relative to other item's whose **width** is also # in the 0.0 to 1.0 range. For example, if you have two item's with a width # of 0.1, and one item with a width of 0.2, the first two items will each # receive 25% of the available width, while the third item will receive # 50% of the available width. The available width is the total width of the # view minus the width of any item's with fixed pixel sizes (i.e. width # values not in the 0.0 to 1.0 range). width = Float(-1.0) # Requested height of the editor (in pixels or fraction of available # height). For pixel values (i.e. values not in the range from 0.0 to 1.0), # the actual displayed height is at least the maximum of **height** and the # optimal height of the widget as calculated by the GUI toolkit. Specify a # negative value to ignore the toolkit's optimal height. For example, use # -50 to force a height of 50 pixels. The default value of -1 ensures that # the toolkit's optimal height is used. # # A value in the range from 0.0 to 1.0 specifies the fraction of the # available height to assign to the editor. Note that the value is not an # absolute value, but is relative to other item's whose **height** is also # in the 0.0 to 1.0 range. For example, if you have two item's with a height # of 0.1, and one item with a height of 0.2, the first two items will each # receive 25% of the available height, while the third item will receive # 50% of the available height. The available height is the total height of # the view minus the height of any item's with fixed pixel sizes (i.e. # height values not in the 0.0 to 1.0 range). height = Float(-1.0) # The extended trait name of the trait containing the item's invalid state # status (passed through to the item's editor): invalid = Str #--------------------------------------------------------------------------- # Initialize the object: #--------------------------------------------------------------------------- def __init__(self, value=None, **traits): """ Initializes the item object. """ super(Item, self).__init__(**traits) if value is None: return if not isinstance(value, basestring): raise TypeError, ( "The argument to Item must be a string of the " "form: [id:][object.[object.]*][name]['['label']']`tooltip`" "[<width[,height]>][#^][$|@|*|~|;style]") value, empty = self._parse_label(value) if empty: self.show_label = False value = self._parse_style(value) value = self._parse_size(value) value = self._parse_tooltip(value) value = self._option(value, '#', 'resizable', True) value = self._option(value, '^', 'emphasized', True) value = self._split('id', value, ':', find, 0, 1) value = self._split('object', value, '.', rfind, 0, 1) if value != '': self.name = value #--------------------------------------------------------------------------- # Returns whether or not the object is replacable by an Include object: #--------------------------------------------------------------------------- def is_includable(self): """ Returns a Boolean indicating whether the object is replaceable by an Include object. """ return (self.id != '') #--------------------------------------------------------------------------- # Returns whether or not the Item represents a spacer or separator: #--------------------------------------------------------------------------- def is_spacer(self): """ Returns True if the item represents a spacer or separator. """ name = self.name.strip() return ((name == '') or (name == '_') or (all_digits.match(name) is not None)) #--------------------------------------------------------------------------- # Gets the help text associated with the Item in a specified UI: #--------------------------------------------------------------------------- def get_help(self, ui): """ Gets the help text associated with the Item in a specified UI. """ # Return 'None' if the Item is a separator or spacer: if self.is_spacer(): return None # Otherwise, it must be a trait Item: if self.help != '': return self.help object = eval(self.object_, globals(), ui.context) return object.base_trait(self.name).get_help() #--------------------------------------------------------------------------- # Gets the label to use for a specified Item in a specified UI: #--------------------------------------------------------------------------- def get_label(self, ui): """ Gets the label to use for a specified Item. If not specified, the label is set as the name of the corresponding trait, replacing '_' with ' ', and capitalizing the first letter (see :func:`user_name_for`). This is called the *user name*. Magic: - if attr:`item.label` is specified, and it begins with '...', the final label is the user name followed by the item label - if attr:`item.label` is specified, and it ends with '...', the final label is the item label followed by the user name """ # Return 'None' if the Item is a separator or spacer: if self.is_spacer(): return None label = self.label if label != '': return label name = self.name object = eval(self.object_, globals(), ui.context) trait = object.base_trait(name) label = user_name_for(name) tlabel = trait.label if tlabel is None: return label if isinstance(tlabel, basestring): if tlabel[0:3] == '...': return label + tlabel[3:] if tlabel[-3:] == '...': return tlabel[:-3] + label if self.label != '': return self.label return tlabel return tlabel(object, name, label) #--------------------------------------------------------------------------- # Returns an id used to identify the item: #--------------------------------------------------------------------------- def get_id(self): """ Returns an ID used to identify the item. """ if self.id != '': return self.id return self.name #--------------------------------------------------------------------------- # Parses a '<width,height>' value from the string definition: #--------------------------------------------------------------------------- def _parse_size(self, value): """ Parses a '<width,height>' value from the string definition. """ match = size_pat.match(value) if match is not None: data = match.group(2) value = match.group(1) + match.group(3) col = data.find(',') if col < 0: self._set_float('width', data) else: self._set_float('width', data[:col]) self._set_float('height', data[col + 1:]) return value #--------------------------------------------------------------------------- # Parses a '`tooltip`' value from the string definition: #--------------------------------------------------------------------------- def _parse_tooltip(self, value): """ Parses a *tooltip* value from the string definition. """ match = tooltip_pat.match(value) if match is not None: self.tooltip = match.group(2) value = match.group(1) + match.group(3) return value #--------------------------------------------------------------------------- # Sets a specified trait to a specified string converted to a float: #--------------------------------------------------------------------------- def _set_float(self, name, value): """ Sets a specified trait to a specified string converted to a float. """ value = value.strip() if value != '': setattr(self, name, float(value)) #--------------------------------------------------------------------------- # Returns a 'pretty print' version of the Item: #--------------------------------------------------------------------------- def __repr__(self): """ Returns a "pretty print" version of the Item. """ options = self._repr_options('id', 'object', 'label', 'style', 'show_label', 'width', 'height') if options is None: return "Item( '%s' )" % self.name return "Item( '%s'\n%s\n)" % (self.name, self._indent( options, ' '))
class TableModel(GridModel): """ Model for table data. """ # ------------------------------------------------------------------------- # Trait definitions: # ------------------------------------------------------------------------- #: The editor that created this model editor = Instance(Editor) #: The current filter filter = Instance(TableFilter, allow_none=True) #: Current filter summary message filter_summary = Str("All items") #: Display the table items in reverse order? reverse = Bool(False) #: Event fired when the table has been sorted sorted = Event #: The current 'auto_add' row auto_add_row = Any def __init__(self, **traits): """ Initializes the object. """ super(TableModel, self).__init__(**traits) # Attach trait handlers to the list object: editor = self.editor object = editor.context_object name = " " + editor.extended_name # Set up listeners for any of the model data changing: object.on_trait_change(self._on_data_changed, name, dispatch="ui") object.on_trait_change( self.fire_content_changed, name + ".-", dispatch="ui" ) # Set up listeners for any column definitions changing: editor.on_trait_change(self.update_columns, "columns", dispatch="ui") editor.on_trait_change( self.update_columns, "columns_items", dispatch="ui" ) # Initialize the current filter from the editor's default filter: self.filter = editor.filter # If we are using 'auto_add' mode, create the first 'auto_add' row: if editor.auto_add: self.auto_add_row = row = editor.create_new_row() if row is not None: row.on_trait_change(self.on_auto_add_row, dispatch="ui") # -- TableModel Interface ------------------------------------------------- def dispose(self): """ Disposes of the model when it is no longer needed. """ editor = self.editor object = editor.context_object name = " " + editor.extended_name # Remove listeners for any of the model data changing: object.on_trait_change(self._on_data_changed, name, remove=True) object.on_trait_change( self.fire_content_changed, name + ".-", remove=True ) # Remove listeners for any column definitions changing: editor.on_trait_change(self.update_columns, "columns", remove=True) editor.on_trait_change( self.update_columns, "columns_items", remove=True ) # Make sure we have removed listeners from the current filter also: if self.filter is not None: self.filter.on_trait_change(self._filter_modified, remove=True) # Clean-up any links that should be broken: self.editor = None def get_filtered_items(self): """ Returns all model items matching the current filter. """ return self.__filtered_items() def get_filtered_item(self, index=0): """ Returns a single specified item from those items matching the current filter. """ try: return self.__filtered_items()[index] except: logger.error( "TableModel error: Request for invalid row %d out of " "%d" % (index, len(self.__filtered_items())) ) return None def raw_index_of(self, row): """ Returns the raw, unfiltered index corresponding to a specified filtered index. """ if self._filtered_cache is None: return row return self.editor.filtered_indices[row] def insert_filtered_item_after(self, index, item): """ Inserts an object after a specified filtered index. """ mapped_index = 0 n = len(self.editor.filtered_indices) if index >= n: if (index != 0) or (n != 0): raise IndexError elif index >= 0: mapped_index = self.editor.filtered_indices[index] + 1 self.__items().insert(mapped_index, item) sorted = self._sort_model() if sorted: mapped_index = self.__items().index(item) self._filtered_cache = None return (mapped_index, sorted) def delete_filtered_item_at(self, index): """ Deletes the object at the specified filtered index. """ if index >= len(self.editor.filtered_indices): raise IndexError mapped_index = self.editor.filtered_indices[index] items = self.__items() object = items[mapped_index] del items[mapped_index] self._filtered_cache = None return (mapped_index, object) def update_columns(self): """ Updates the table view when columns have been changed. """ self._columns = None self.fire_structure_changed() self.editor.refresh() def no_column_sort(self): """ Resets any sorting being performed on the underlying model. """ self._sorter = self._filtered_cache = None self.column_sorted = GridSortEvent(index=-1) # self.fire_structure_changed() # -- Event Handlers ------------------------------------------------------- @on_trait_change("filter.+") def _filter_modified(self): """ Handles the contents of the filter being changed. """ self._filtered_cache = None self.fire_structure_changed() self.editor.filter_modified() def _click_changed(self, event): """ Handles the grid firing a 'click' event. """ row, col = event # Fire the same event on the editor after mapping it to a model object # and column name: object = self.get_filtered_item(row) column = self.__get_column(col) self.editor.click = (object, column) # Check to see if the column has a view to display: view = column.get_view(object) if view is not None: column.get_object(object).edit_traits( view=view, parent=self._bounds_for(row, col) ) # Invoke the column's click handler: column.on_click(object) def _dclick_changed(self, event): """ Handles the grid firing a 'dclick' event. """ row, col = event # Fire the same event on the editor after mapping it to a model object # and column name: object = self.get_filtered_item(row) column = self.__get_column(col) self.editor.dclick = (object, column) # Invoke the column's double-click handler: column.on_dclick(object) def on_auto_add_row(self): """ Handles the user modifying the current 'auto_add' mode row. """ object = self.auto_add_row object.on_trait_change(self.on_auto_add_row, remove=True) self.auto_add_row = row = self.editor.create_new_row() if row is not None: row.on_trait_change(self.on_auto_add_row, dispatch="ui") do_later( self.editor.add_row, object, len(self.get_filtered_items()) - 2 ) # -- GridModel Interface -------------------------------------------------- def get_column_count(self): """ Returns the number of columns for this table. """ return len(self.__get_columns()) def get_column_name(self, index): """ Returns the label of the column specified by the (zero-based) index. """ return self.__get_column(index).get_label() def get_column_size(self, index): """ Returns the size in pixels of the column indexed by *index*. A value of -1 or None means to use the default. """ return self.__get_column(index).get_width() def get_cols_drag_value(self, cols): """ Returns the value to use when the specified columns are dragged or copied and pasted. The parameter *cols* is a list of column indexes. """ return [self.__get_data_column(col) for col in cols] def get_cols_selection_value(self, cols): """ Returns a list of TraitGridSelection objects containing the objects corresponding to the grid rows and the traits corresponding to the specified columns. """ values = [] for obj in self.__items(False): values.extend( [ TraitGridSelection( obj=obj, name=self.__get_column_name(col) ) for col in cols ] ) return values def sort_by_column(self, col, reverse=False): """ Sorts the model data by the column indexed by *col*. """ # Make sure we allow sorts by column: factory = self.editor.factory if not factory.sortable: return # Flush the object cache: self._filtered_cache = None # Cache the sorting information for later: self._sorter = self.__get_column(col).key self._reverse = reverse # If model sorting is requested, do it now: self._sort_model() # Indicate the we have been sorted: self.sorted = True self.column_sorted = GridSortEvent(index=col, reversed=reverse) def is_column_read_only(self, index): """ Returns True if the column specified by the zero-based *index* is read-only. """ return not self.__get_column(index).editable def get_row_count(self): """ Return the number of rows for this table. """ return len(self.__filtered_items()) def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) *index*. """ return "<undefined>" def get_rows_drag_value(self, rows): """ Returns the value to use when the specified rows are dragged or copied and pasted. The parameter *rows* is a list of row indexes. If there is only one row listed, then return the corresponding trait object. If more than one row is listed, then return a list of objects. """ items = self.__filtered_items() return [items[row] for row in rows] def get_rows_selection_value(self, rows): """ Returns a list of TraitGridSelection objects containing the object corresponding to the selected rows. """ items = self.__filtered_items() return [TraitGridSelection(obj=items[row]) for row in rows] def is_row_read_only(self, index): """ Returns True if the row specified by the zero-based *index* is read-only. """ return False def get_cell_editor(self, row, col): """ Returns the editor for the specified cell. """ if self.editor is None: return None column = self.__get_column(col) object = self.get_filtered_item(row) editor = column.get_editor(object) if editor is None: return None editor._ui = self.editor.ui target, name = column.target_name(object) return TraitGridCellAdapter( editor, target, name, "", context=self.editor.ui.context, style=column.get_style(object), width=column.get_edit_width(object), height=column.get_edit_height(object), ) def get_cell_renderer(self, row, col): """ Returns the renderer for the specified cell. """ return self.__get_column(col).get_renderer(self.get_filtered_item(row)) def get_cell_drag_value(self, row, col): """ Returns the value to use when the specified cell is dragged or copied and pasted. """ return self.__get_column(col).get_drag_value( self.get_filtered_item(row) ) def get_cell_selection_value(self, row, col): """ Returns a TraitGridSelection object specifying the data stored in the table at (*row*, *col*). """ return TraitGridSelection( obj=self.get_filtered_item(row), name=self.__get_column_name(col) ) def resolve_selection(self, selection_list): """ Returns a list of (row, col) grid-cell coordinates that correspond to the objects in *selection_list*. For each coordinate, if the row is -1, it indicates that the entire column is selected. Likewise coordinates with a column of -1 indicate an entire row that is selected. For the TableModel, the objects in *selection_list* must be TraitGridSelection objects. """ items = self.__filtered_items() cells = [] for selection in selection_list: row = -1 if selection.obj is not None: try: row = items.index(selection.obj) except ValueError: continue column = -1 if selection.name != "": column = self._get_column_index_by_trait(selection.name) if column is None: continue cells.append((row, column)) return cells def get_cell_context_menu(self, row, col): """ Returns a Menu object that generates the appropriate context menu for this cell. """ column = self.__get_column(col) menu = column.get_menu(self.get_filtered_item(row)) editor = self.editor if menu is None: menu = editor.factory.menu if menu is None: menu_name = editor.factory.menu_name if menu_name: menu = getattr(self.editor.object, menu_name, None) if menu is not None: editor.prepare_menu(row, column) return (menu, editor) return None def get_value(self, row, col): """ Returns the value stored in the table at (*row*, *col*). """ object = self.get_filtered_item(row) if object is self.auto_add_row: return "" value = self.__get_column(col).get_value(object) formats = self.__get_column_formats(col) if (value is not None) and (formats is not None): format = formats.get(type(value)) if format is not None: try: if callable(format): value = format(value) else: value = format % value except: pass return value def is_valid_cell_value(self, row, col, value): """ Tests whether *value* is valid for the cell at (*row*, *col*). Returns True if value is acceptable, and False otherwise. """ return self.__get_column(col).is_droppable( self.get_filtered_item(row), value ) def is_cell_empty(self, row, col): """ Returns True if the cell at (*row*, *col*) has a None value, and False otherwise. """ return self.get_value(row, col) is None def is_cell_read_only(self, row, col): """ Returns True if the cell at (*row*, *col*) is read-only, and False otherwise. """ return not self.__get_column(col).is_editable( self.get_filtered_item(row) ) def get_cell_bg_color(self, row, col): """ Returns a wxColour object specifying the background color of the specified cell. """ return self.__get_column(col).get_cell_color( self.get_filtered_item(row) ) def get_cell_text_color(self, row, col): """ Returns a wxColour object specifying the text color of the specified cell. """ column = self.__get_column(col) item = self.get_filtered_item(row) return column.get_text_color(item) def get_cell_font(self, row, col): """ Returns a wxFont object specifying the font of the specified cell. """ return self.__get_column(col).get_text_font( self.get_filtered_item(row) ) def get_cell_halignment(self, row, col): """ Returns a string specifying the horizontal alignment of the specified cell. Returns 'left' for left alignment, 'right' for right alignment, or 'center' for center alignment. """ return self.__get_column(col).get_horizontal_alignment( self.get_filtered_item(row) ) def get_cell_valignment(self, row, col): """ Returns a string specifying the vertical alignment of the specified cell. Returns 'top' for top alignment, 'bottom' for bottom alignment, or 'center' for center alignment. """ return self.__get_column(col).get_vertical_alignment( self.get_filtered_item(row) ) def _insert_rows(self, pos, num_rows): """ Inserts *num_rows* at *pos*; fires an event only if a factory method for new rows is defined or the model is not empty. Otherwise, it returns 0. """ count = 0 factory = self.editor.factory.row_factory if factory is None: items = self.__items(False) if len(items) > 0: factory = items[0].__class__ if factory is not None: new_data = [ x for x in [factory() for i in range(num_rows)] if x is not None ] if len(new_data) > 0: count = self._insert_rows_into_model(pos, new_data) self.rows_added = ("added", pos, new_data) return count def _delete_rows(self, pos, num_rows): """ Removes rows *pos* through *pos* + *num_rows* from the model. """ row_count = self.get_rows_count() if (pos + num_rows) > row_count: num_rows = row_count - pos return self._delete_rows_from_model(pos, num_rows) def _set_value(self, row, col, value): """ Sets the value of the cell at (*row*, *col*) to *value*. Raises a ValueError if the value is vetoed or the cell at the specified position does not exist. """ new_rows = 0 column = self.__get_column(col) obj = None try: obj = self.get_filtered_item(row) except: # Add a new row: new_rows = self._insert_rows(self.get_row_count(), 1) if new_rows > 0: # Now set the value on the new object: try: obj = self.get_filtered_item(self.get_row_count() - 1) except: # fixme: what do we do in this case? veto the set somehow? # raise an exception? pass if obj is not None: self._set_data_on_row(obj, column, value) return new_rows def _move_column(self, frm, to): """ Moves a specified **frm** column to before the specified **to** column. Returns **True** if successful; **False** otherwise. """ to_column = None if to < len(self.__get_columns()): to_column = self.__get_column(to) return self.editor.move_column(self.__get_column(frm), to_column) def _set_data_on_row(self, row, column, value): """ Sets the cell specified by (*row*, *col*) to *value, which can be either a member of the row object, or a no-argument method on that object. """ column.set_value(row, value) def _insert_rows_into_model(self, pos, new_data): """ Inserts the given new rows into the model. """ raw_pos = self.raw_index_of(pos) self.__items()[raw_pos:raw_pos] = new_data def _delete_rows_from_model(self, pos, num_rows): """ Deletes the specified rows from the model. """ raw_rows = sorted( [self.raw_index_of(i) for i in range(pos, pos + num_rows)] ) raw_rows.reverse() items = self.__items() for row in raw_rows: del items[row] return num_rows def _on_data_changed(self): """ Forces the grid to refresh when the underlying list changes. """ # Invalidate the current cache (if any): self._filtered_cache = None self.fire_structure_changed() def _mouse_cell_changed(self, new): """ Handles the user mousing over a specified cell. """ row, col = new column = self.__get_column(col) object = self.get_filtered_item(row) # Update the tooltip if necessary: tooltip = column.get_tooltip(object) if tooltip != self._tooltip: self._tooltip = tooltip self.editor.grid._grid_window.SetToolTip(wx.ToolTip(tooltip)) if column.is_auto_editable(object): x, y, dx, dy = self._bounds_for(row, col) if column.is_editable(object): view = View( Item( name=column.name, editor=column.get_editor(object), style=column.get_style(object), show_label=False, padding=-4, ), kind="info", width=dx, height=dy, ) else: view = column.get_view(object) if view is None: return column.get_object(object).edit_traits( view=view, parent=(x, y, dx, dy) ) def _bounds_for(self, row, col): """ Returns the coordinates and size of the specified cell in the form: ( x, y, dx, dy ). """ grid = self.editor.grid coords = wxg.GridCellCoords(row, col) x, y, dx, dy = grid._grid.BlockToDeviceRect(coords, coords) x, y = grid._grid_window.ClientToScreenXY(x, y) return (x, y, dx, dy) def _sort_model(self): """ Sorts the underlying model if that is what the user requested. """ editor = self.editor sorted = editor.factory.sort_model and (self._sorter is not None) if sorted: items = self.__items(False)[:] items.sort(key=self._sorter) if self.reverse ^ self._reverse: items.reverse() editor.value = items return sorted def __items(self, ordered=True): """ Returns the raw list of model objects. """ result = self.editor.value if not isinstance(result, SequenceTypes): return [result] if ordered and self.reverse: return ReversedList(result) return result def __filtered_items(self): """ Returns the list of all model objects that pass the current filter. """ fc = self._filtered_cache if fc is None: items = self.__items() filter = self.filter if filter is None: nitems = [nitem for nitem in enumerate(items)] self.filter_summary = "All %s items" % len(nitems) else: if not callable(filter): filter = filter.filter nitems = [ nitem for nitem in enumerate(items) if filter(nitem[1]) ] self.filter_summary = "%s of %s items" % ( len(nitems), len(items), ) sorter = self._sorter if sorter is not None: nitems.sort(key=lambda x: sorter(x[1])) if self._reverse: nitems.reverse() self.editor.filtered_indices = [x[0] for x in nitems] self._filtered_cache = fc = [x[1] for x in nitems] if self.auto_add_row is not None: self._filtered_cache.append(self.auto_add_row) return fc def __get_data_column(self, col): """ Returns a list of model data from the column indexed by *col*. """ column = self.__get_column(col) return [column.get_value(item) for item in self.__filtered_items()] def __get_columns(self): columns = self._columns if columns is None: self._columns = columns = [ c for c in self.editor.columns if c.visible ] return columns def __get_column(self, col): try: return self.__get_columns()[col] except: return self.__get_columns()[0] def __get_column_name(self, col): return self.__get_column(col).name def __get_column_formats(self, col): return None # Not used/implemented currently def _get_column_index_by_trait(self, name): for i, col in enumerate(self.__get_columns()): if name == col.name: return i
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') use_high_res_head = Bool(True) # 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: err = ("No subjects directory is selected. Please specify " "subjects_dir first.") raise RuntimeError(err) fs_home = get_fs_home() if fs_home is None: err = ("FreeSurfer contains files that are needed for copying the " "fsaverage brain. Please install FreeSurfer and try again.") raise RuntimeError(err) create_default_subject(fs_home=fs_home, subjects_dir=self.subjects_dir) self.refresh = True self.use_high_res_head = False 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 EditableValue(AbstractValueType): """ A base class for editable values. This class provides two things beyond the base AbstractValueType: a trait ``is_editable`` which allows toggling editing state on and off, and an ``is_valid`` method that is used for validation before setting a value. """ #: Whether or not the value is editable, assuming the underlying data can #: be set. is_editable = Bool(True, update_value_type=True) def is_valid(self, model, row, column, value): """ Whether or not the value is valid for the data item specified. The default implementation returns True for all values. Parameters ---------- model : AbstractDataModel The data model holding the data. row : sequence of int The row in the data model being queried. column : sequence of int The column in the data model being queried. value : any The value to validate. Returns ------- is_valid : bool Whether or not the value is valid. """ return True # AbstractValueType Interface -------------------------------------------- def has_editor_value(self, model, row, column): """ Return whether or not the value can be edited. A cell is editable if the underlying data can be set, and the ``is_editable`` flag is set to True Parameters ---------- model : AbstractDataModel The data model holding the data. row : sequence of int The row in the data model being queried. column : sequence of int The column in the data model being queried. Returns ------- has_editor_value : bool Whether or not the value is editable. """ return model.can_set_value(row, column) and self.is_editable def set_editor_value(self, model, row, column, value): """ Set the edited value. Parameters ---------- model : AbstractDataModel The data model holding the data. row : sequence of int The row in the data model being set. column : sequence of int The column in the data model being set. value : any The value being set. Raises ------- DataViewSetError If the value cannot be set. """ if self.is_valid(model, row, column, value): model.set_value(row, column, value) else: raise DataViewSetError("Invalid value set: {!r}".format(value))