class Observer(trellis.Component): observee = trellis.make(trellis.Set, writable=True) observations = trellis.make(list) @trellis.perform def observer(self): def map(iterable): return sorted(x.value for x in iterable) self.observations.append(map(self.observee)) if self.observee.added: self.observations.append(("added", map(self.observee.added))) if self.observee.removed: self.observations.append(("removed", map(self.observee.removed)))
class NameSorter(trellis.Component): input = trellis.make(list) @trellis.maintain(optional=True) def output(self): # print 'name output running', id(self) return sorted(self.input, key=lambda x: x.name)
class ReminderList(ItemAddOn): reminders = trellis.make(trellis.List) def add_reminder(self, **kwargs): reminder = Reminder(item=self._item, **kwargs) self.reminders.append(reminder) return reminder def remove_all_reminders(self): self.reminders[:] = []
class ItemKeywords(ItemAddOn): keyword_strings = trellis.make(trellis.Set) @trellis.maintain def maintenance(self): """Observe keyword_strings, maintain inverse links from Keyword objects.""" for word in self.keyword_strings.added: Keyword(word).items.add(self._item) for word in self.keyword_strings.removed: Keyword(word).items.remove(self._item)
class ChandlerFrame(core.Frame): model = trellis.make(trellis.Set, writable=True) @trellis.maintain def sidebar(self): return sidebar.Sidebar(scope=self, model=self.model) @trellis.maintain def dashboard(self): return dashboard.Dashboard(scope=self, model=dashboard.AppEntryAggregate(input=self.sidebar.filtered_items))
class Sum(trellis.Component): sortfunc = lambda self, x: x input = trellis.make(dict) @trellis.maintain def output(self): return sum([ self.input[k].output for k in sorted(self.input.keys(), key=self.sortfunc) ], [])
class Merger(trellis.Component): input = trellis.make(list) key = trellis.attr() #attrgetter('name') @trellis.maintain def output(self): if len(self.input) > 1: return reduce(lambda *a, **k: merge(*a, **dict(key=self.key)), self.input) elif len(self.input): return self.input[0] else: return []
class DJoiner(trellis.Component): input = trellis.make(list) @trellis.maintain def output(self): d = defaultdict(list) for splitter in self.input: o = splitter.output for key, val in o.iteritems(): d[key].append(val.output) for k, v in d.iteritems(): d[k] = Merger(input=v, key=attrgetter('name')) return dict(d)
class CV(trellis.Component): v = trellis.attr(False) s = trellis.make(trellis.Set, name='s') @trellis.maintain def maintain(self): if self.v: self.s.add(1) else: self.s.discard(1) @trellis.perform def perform(s): self.assertEqual(s.v, True)
class ModificationRecipe(trellis.Component): # a dictionary of (AddOn, attr_name) keys, values override master changes = trellis.make(trellis.Dict) @trellis.modifier def make_change(self, add_on, name, value): key = (add_on, name) self.changes[key] = value @trellis.modifier def remove_change(self, add_on, name): key = (add_on, name) if key in self.changes: del self.changes[key]
class EIM(Extension): uuid = trellis.make(lambda x: uuid4()) well_known_name = trellis.compute(lambda self: self._well_known_name) trellis.attrs( _well_known_name=None, ical_uid=None, ical_extra=None, ) @trellis.modifier def add(self, name=None, **kw): super(EIM, self).add(**kw) if name is None: name = getattr(self._item, 'well_known_name', None) if name is not None: self._well_known_name = name set_item_for_name(name, self.item) set_item_for_uuid(self.uuid, self.item) return self
class BindingValueListener(protocols.Adapter, trellis.Component): protocols.advise(asAdapterForProtocols=[IBindableValue], instancesProvide=[IValueListener]) def __init__(self, subject): protocols.Adapter.__init__(self, subject) trellis.Component.__init__(self) # self.value_changed() self.subject.bind(self.value_changed) def value_changed(self): self.value = self.get_value() def get_value(self): return self.subject.value value = trellis.make(rule=get_value, writable=True, optional=True, name='value')
class Splitter(trellis.Component): input = trellis.make(list) basesort = trellis.attr() #NameSorter spliton = trellis.attr() #attrgetter('status') @trellis.maintain def output(self): # print "partitions running" if not self.input: return {} ret = dict() for b in self.input: stat = self.spliton(b) if stat not in ret: ret[stat] = n = self.basesort() else: n = ret[stat] n.input.append(b) # print len(ret), "partitions" return ret
class _Keyword(Entity): word = trellis.attr() items = trellis.make(trellis.Set) def __init__(self, word, **kwds): self.word = word super(_Keyword, self).__init__(**kwds) def __repr__(self): return "<Keyword: %s>" % self.word def add(self, item): ItemKeywords(item).keyword_strings.add(self.word) def remove(self, item): ItemKeywords(item).keyword_strings.remove(self.word) @trellis.compute def title(self): return "Tag: %s" % self.word @trellis.compute def well_known_name(self): return KEYWORD_PREFIX + self.word
class C2(trellis.Component): c1 = trellis.make(C1, name='c1') @trellis.compute def calc(self): return self.c1.x
class ChandlerApplication(runtime.Application): """The Chandler Application""" context.replaces(runtime.Application) sidebar_entries = trellis.make(trellis.Set, writable=True) top_level = trellis.make(trellis.Set)
class TablePresentation(trellis.Component, wxGrid.PyGridTableBase): table = trellis.make(core.Table, writable=True) @trellis.perform def update_column_selection(self): for index, column in enumerate(self.table.columns): if column is self.table.sort_column: show_arrows = column.hints.get('sort_arrows', True) self.View.SetSelectedCol(index) self.View.SetUseColSortArrows(show_arrows) self.View.SetColSortAscending(self.table.items.reverse) break @trellis.perform def update_grid(self): view = self.GetView() view.BeginBatch() for start, end, newLen in self.table.items.changes: oldLen = end - start if newLen == oldLen: view.ProcessTableMessage( wxGrid.GridTableMessage( self, wxGrid.GRIDTABLE_REQUEST_VIEW_GET_VALUES, start, oldLen)) elif newLen > oldLen: if oldLen > 0: view.ProcessTableMessage( wxGrid.GridTableMessage( self, wxGrid.GRIDTABLE_REQUEST_VIEW_GET_VALUES, start, oldLen)) view.ProcessTableMessage( wxGrid.GridTableMessage( self, wxGrid.GRIDTABLE_NOTIFY_ROWS_INSERTED, start + oldLen, newLen - oldLen)) else: view.ProcessTableMessage( wxGrid.GridTableMessage( self, wxGrid.GRIDTABLE_NOTIFY_ROWS_DELETED, start + newLen, oldLen - newLen)) if newLen > 0: view.ProcessTableMessage( wxGrid.GridTableMessage( self, wxGrid.GRIDTABLE_REQUEST_VIEW_GET_VALUES, start, newLen)) for key in self.table.observer.changes: row, col = key if row < len(self.table.model): view.ProcessTableMessage( wxGrid.GridTableMessage( self, wxGrid.GRIDTABLE_REQUEST_VIEW_GET_VALUES, row, 1)) view.EndBatch() for row in view.GetSelectedRows(): index = self.RowToIndex(row) if not self.table.items[index] in self.table.selection: trellis.on_commit(view.DeselectRow, row) for index, item in enumerate(self.table.items): row = self.IndexToRow(index) if item in self.table.selection: trellis.on_commit(view.SelectRow, row, True) defaultRWAttribute = wxGrid.GridCellAttr() defaultROAttribute = wxGrid.GridCellAttr() defaultROAttribute.SetReadOnly(True) def __init__(self, grid, **kw): wxGrid.PyGridTableBase.__init__(self) trellis.Component.__init__(self, **kw) grid.SetTable(self, selmode=wxGrid.Grid.SelectRows) self.SetView(grid) if self.table.hints.get('column_headers'): grid.SetColLabelSize(wxGrid.GRID_DEFAULT_COL_LABEL_HEIGHT) else: grid.SetColLabelSize(0) for index, column in enumerate(self.table.columns or ()): grid.SetColLabelValue(index, column.label) width = column.hints.get('width', 120) grid.SetColMinimalWidth(index, width) grid.SetColSize(index, width) if EXTENDED_WX: scaleColumn = grid.GRID_COLUMN_SCALABLE if column.hints.get( 'scalable') else grid.GRID_COLUMN_NON_SCALABLE grid.ScaleColumn(index, scaleColumn) grid.Bind(wxGrid.EVT_GRID_RANGE_SELECT, self.OnRangeSelect) grid.Bind(wxGrid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClicked) grid.Bind(wx.EVT_SIZE, self.OnSize) grid.Bind(wx.EVT_SCROLLWIN, self.OnScroll) def GetNumberRows(self): return len(self.table.items) def GetNumberCols(self): return len(self.table.columns) def RowToIndex(self, row): return row def IndexToRow(self, index): return index def RangeToIndex(self, startRow, endRow): """ Translasted a row range in the grid to a row range in the collection """ return self.RowToIndex(startRow), self.RowToIndex(endRow) def IsEmptyCell(self, row, col): return False def CanClick(self, row, col): column = self.table.columns[col] return column.action is not None def OnClick(self, row, col): selection = [self.table.items[self.RowToIndex(row)]] self.table.columns[col].action(selection) def TrackMouse(self, row, col): return True def GetToolTipString(self, row, col): pass def ReadOnly(self, row, col): return self.table.columns[col].set_text_value is None def GetValue(self, row, col): return self.table.get_cell_value((row, col)) def SetValue(self, row, col, value): obj = self.table.items[self.RowToIndex(row)] self.table.columns[col].set_text_value(obj, value) def GetColLabelValue(self, col): return self.table.columns[col].label def GetColLabelBitmap(self, col): return getattr(self.table.columns[col], 'bitmap', None) _deselected_all = False def OnSize(self, event): event.Skip() # We have to do this because wx will send EVT_SIZE events from # other calls (e.g. View.EndBatch() in our performer) wx.CallAfter(self._update_visible) def OnScroll(self, event): event.Skip() wx.CallAfter(self._update_visible) @trellis.modifier def _update_visible(self): # We need the check on self.View because we call # this with a wx.CallAfter, and that call will proceed # even if the View has been torn down. if self.View: rect = self.View.GetClientRect() if rect.Height > 0 and rect.Width > 0: x, y = self.View.CalcUnscrolledPosition(rect.X, rect.Y) start_row = max(self.View.YToRow(y), 0) start_col = max(self.View.XToCol(x), 0) x, y = self.View.CalcUnscrolledPosition( rect.Right, rect.Bottom) end_row = self.View.YToRow(y) end_col = self.View.XToCol(x) if end_row < 0: end_row = len(self.table.model) if end_col < 0: end_col = len(self.table.columns) vr = self.table.visible_ranges increments = (start_row - vr[0], (end_row - start_row) - vr[1], start_col - vr[2], (end_col - start_col) - vr[3]) self.table.visible_range_increments = increments def OnRangeSelect(self, event): """ Synchronize the grid's selection back into the Table """ firstRow = event.TopRow lastRow = event.BottomRow indexStart, indexEnd = self.RangeToIndex(firstRow, lastRow) selecting = event.Selecting() if (not selecting and firstRow == 0 and lastRow != 0 and lastRow == self.GetNumberRows() - 1): # this is a special "deselection" event that the # grid sends us just before selecting another # single item. This happens just before a user # simply clicks from one item to another. self._deselected_all = True # [@@@] grant: Need to avoid broadcasting a # selection change in this case. event.Skip() return if selecting and self.table.single_item_selection: new_selection = self.table.items[indexEnd] if self.table.selected_item != new_selection: self.table.selected_item = new_selection else: items = set(self.table.items[index] for index in xrange(indexStart, indexEnd + 1)) if selecting: if self._deselected_all: self.table.new_selection = items else: items.difference_update(self.table.selection) self.table.selection.update(items) else: self.table.selection.difference_update(items) if self._deselected_all: del self._deselected_all event.Skip() def SelectedRowRanges(self): # @@@ [grant] Move this (or something similar) to Table """ Uses IndexRangeToRowRange to convert the selected indexes to selected rows """ lastRow = None # This is O(N) tsk for index, item in enumerate(self.table.items): itemSelected = (item in self.table.selection) if itemSelected: if lastRow is None: lastRow = self.IndexToRow(index) else: pass elif lastRow is not None: yield lastRow, self.IndexToRow(index - 1) lastRow = None if lastRow is not None: yield lastRow, self.IndexToRow(len(self.table.items) - 1) def OnLabelLeftClicked(self, event): assert (event.GetRow() == -1 ) # Currently Table only supports column headers column = self.table.columns[event.GetCol()] # A "receiver" style cell: it will take care of toggling # the sort, etc. self.table.select_column = column def GetTypeName(self, row, column): return self.table.columns[column].hints.get('type', 'String') def GetAttr(self, row, column, kind): attribute = super(TablePresentation, self).GetAttr(row, column, kind) if attribute is None: attribute = self.defaultROAttribute grid = self.GetView() if (not self.table.columns[column].hints.get('readonly', False) and not self.ReadOnly(row, column)): attribute = self.defaultRWAttribute attribute.IncRef() return attribute