class SetCollection(ContentCollection): """ A collection class to wrap an arbitrary set that doesn't include trash """ __metaclass__ = schema.CollectionClass __collection__ = 'set' set = schema.One(schema.TypeReference('//Schema/Core/AbstractSet')) def withoutTrash(self, copy=True): return self
class KindCollection(ContentCollection): """ A ContentCollection of all of the items of a particular kind. The C{kind} attribute determines the C{Kind} of the items in the C{KindCollection}. The C{recursive} attribute determines whether items of subkinds are included (C{False}) by default). """ __metaclass__ = schema.CollectionClass __collection__ = 'set' set = schema.One(schema.TypeReference('//Schema/Core/AbstractSet')) kind = schema.One(schema.TypeReference('//Schema/Core/Kind')) recursive = schema.One(schema.Boolean, defaultValue=False) def __setup__(self): setattr(self, self.__collection__, KindSet(self.kind, self.recursive))
class KindCollection(AbstractCollection): """ """ schema.kindInfo( displayName="KindCollection" ) kind = schema.One(schema.TypeReference('//Schema/Core/Kind'), initialValue=None) def contentsUpdated(self, item): # print "KindCollection.contentsUpdated: ",item self._collectionChanged('changed' , 'rep', item) pass def onValueChanged(self, name): if name == "kind": try: self.rep = KindSet(self.kind) except AttributeError: pass
class Sidebar(ControlBlocks.Table): filterKind = schema.One( schema.TypeReference('//Schema/Core/Kind'), initialValue = None, ) # A dictionary of display names of items that don't show as Calendar Views dontShowCalendarForItemsWithName = schema.Mapping (schema.Boolean) # A dictionary mapping a name,kindpathComponent string to a new name. # It would be much nicer if the key could be a (name, kindItem) tuple, but # that's not possible with current parcel XML nameAlternatives = schema.Mapping (schema.String) # A list of the items in the sidebar that are checked checkedItems = schema.Sequence (schema.Item, initialValue = []) # For each button, a list of left offset, right offset and height. # Left offset is the offset in pixels of the left edge of the button # where positive values are from the left edge of the cell rect and # negative values are from the left edge of the cell rect. Height # is the height of the button which is centered vertically in the # cell. A height of zero uses the height of the cell buttonOffsets = schema.Mapping (schema.List, initialValue = {}) editRectOffsets = schema.Sequence (schema.Integer, required = True) schema.addClouds( copying = schema.Cloud(byRef=[filterKind]) ) def instantiateWidget (self): if '__WXMAC__' in wx.PlatformInfo: widget = wxSidebar (self.parentBlock.widget, Block.Block.getWidgetID(self), style=wx.BORDER_SIMPLE) else: widget = wxSidebar (self.parentBlock.widget, Block.Block.getWidgetID(self), style = wx.BORDER_STATIC) widget.RegisterDataType ("Item", SSSidebarRenderer(), SSSidebarEditor("Item")) return widget def onKindParameterizedEvent (self, event): self.filterKind = event.kindParameter # We need to update the click state of the toolbar as well toolbar = Block.Block.findBlockByName("ApplicationBar") for button in toolbar.childrenBlocks: try: buttonEvent = button.event except: continue if buttonEvent == event: button.widget.selectTool() continue self.widget.Refresh() self.postEventByName("SelectItemBroadcast", {'item':self.selectedItemToView}) def onRequestSelectSidebarItemEvent (self, event): # Request the sidebar to change selection # Item specified is usually by name try: item = event.arguments['item'] except KeyError: # find the item by name itemName = event.arguments['itemName'] for item in self.contents: if item.displayName == itemName: break else: return self.postEventByName("SelectItemBroadcast", {'item':item}) return item def onRemoveItemEvent(self, event): """ Permanently remove the collection - we eventually need a user confirmation here """ def deleteItem(item): # TODO: for item collectionsactually call item.delete(), # and also delete any items that exist only in the # doomed itemcollection (garbage collection would be a # big help here) # in the mean time, just remove it. self.contents.remove(item) self.widget.DeleteSelection(deleteItem) onDeleteItemEvent = onRemoveItemEvent def onDeleteEventUpdateUI(self, event): self.onRemoveEventUpdateUI(event) event.arguments['Text'] = _('Delete Collection') """ this is enabled if any user item is selected in the sidebar """ if (self.selectedItemToView and getattr(self.selectedItemToView, 'renameable', True)): event.arguments['Enable'] = True else: event.arguments['Enable'] = False def onRemoveEventUpdateUI(self, event): # doesn't seem to be taking effect? event.arguments['Enabled'] = False def getButtonState (self, buttonName, item): if buttonName == u'Icon': if item in self.checkedItems: return 'Checked' elif buttonName == u'SharingIcon': share = Sharing.getShare(item) if share is not None: if (share.sharer is not None and str(share.sharer.itsPath) == "//userdata/me"): return "Upload" else: return "Download" return "" def setButtonState (self, buttonName, item, value): if buttonName == u'Icon': if item in self.checkedItems: if not value: self.checkedItems.remove (item) else: if value: self.checkedItems.append (item) elif buttonName == u'SharingIcon': """ $$$ We need to hook up sharing here -- DJA """ pass else: assert (False)
class AppCollection(ContentCollection): """ AppCollections implement inclusions, exclusions, source, and trash along with methods for add and remove. """ __metaclass__ = schema.CollectionClass __collection__ = 'set' set = schema.One(schema.TypeReference('//Schema/Core/AbstractSet')) # must be named 'inclusions' to match ListCollection inclusions = inclusions # the exclusions used when no exclusions collection is given collectionExclusions = schema.Sequence(inverse=ContentItem.excludedBy, initialValue=[]) exclusionsCollection = schema.One(inverse=ContentCollection.exclusionsFor, defaultValue=None) trashCollection = schema.One(inverse=ContentCollection.trashFor, defaultValue=None) schema.initialValues( # better hope osaf.pim has been loaded! trash=lambda self: schema.ns('osaf.pim', self.itsView).trashCollection, source=lambda self: None, ) # __collection__ denotes a bi-ref set, # therefore it must be added to the copying cloud def for it to be copied. schema.addClouds(copying=schema.Cloud(byCloud=[exclusionsCollection], byRef=[ trashCollection, __collection__, inclusions, collectionExclusions ]), ) # an AppCollection may have another collection for exclusions and that # other collection may be the global trash collection. If no collection # is specified for exclusions, a local ref collection is used instead. def _getExclusions(self): exclusions = self.exclusionsCollection if exclusions is None: exclusions = self.collectionExclusions return exclusions def _setExclusions(self, exclusions): # Typically we will create a collectionExclusions ref collection; # however, a collection like 'All' will instead want to use the # Trash collection for exclusions if exclusions is None: self.collectionExclusions = [] self.exclusionsCollection = None else: self.exclusionsCollection = exclusions if hasattr(self, 'collectionExclusions'): del self.collectionExclusions self._updateCollection() exclusions = property(_getExclusions, _setExclusions) # an AppCollection may have another collection for trash. If no # collection is given for trash, the collection's exclusions is used # instead following the logic above. def _getTrash(self): trash = self.trashCollection if trash is None: trash = self.exclusions return trash def _setTrash(self, trash): """ In general trash should only be the well known Trash collection or None. None indicates that this collection does not participate in Trash-based activities. The special value of Default for trash is only a sentinel to let us know that nothing has been passed in and that the default trash should be looked up in osaf.pim. During parcel loading, this allows us to pass the trash into the constructor and avoid trying to look it up in osaf.pim while osaf.pim is being loaded. """ # You can designate a certain ListCollection to be used for this # collection's trash; in this case, an additional DifferenceCollection # will be created to remove any trash items from this collection. Any # collections which share a trash get the following benefits: # - Adding an item to the trash will make the item disappear from # collections sharing that trash collection # - When an item is removed from a collection, it will automatically # be moved to the trash if it doesn't appear in any collection which # shares that trash self.trashCollection = trash self._updateCollection() trash = property(_getTrash, _setTrash) def _getSource(self): return self.__source def _setSource(self, source): if hasattr(self, 'source'): self.__source = source self._updateCollection() else: self.__source = source source = property(_getSource, _setSource) def add(self, item): """ Add an item to the collection. """ self.inclusions.add(item) exclusions = self.exclusions if item in exclusions: exclusions.remove(item) # If a trash is associated with this collection, remove the item # from the trash. This has the additional benefit of having the item # reappear in any collection which has the item in its inclusions trash = self.trash if trash is not None and item in trash: trash.remove(item) def remove(self, item): """ Remove an item from the collection. """ isDeleting = item.isDeleting() # never add occurrences to the trash or exclusions lists, bug 10777 isOccurrence = item.itsRefs.get('inheritFrom') is not None # adding to exclusions before determining if the item should be added to # the trash was a problem at one point (bug 4551), but since the mine/ # not-mine mechanism changed, this doesn't seem to be a problem anymore, # and removing from a mine collection was actually misbehaving if the # test was done first, so now logic for moving to the trash has moved # back to after addition to exclusions and removal from inclusions. if not isDeleting and not isOccurrence: self.exclusions.add(item) if item in self.inclusions: self.inclusions.remove(item) trash = self.trash pim_ns = schema.ns('osaf.pim', self.itsView) if not (isDeleting or trash is None or isOccurrence): if isinstance(trash, ContentCollection): for collection in itertools.chain(trash.trashFor, [pim_ns.allCollection]): # allCollection isn't in trash.trashFor, but needs to be # considered if collection is not self and item in collection: # it exists somewhere else, definitely don't add # to trash break else: # we couldn't find it anywhere else, so it goes in the trash trash.add(item) def __setup__(self): # Ensure that the collection is properly set up self._updateCollection() def _updateCollection(self): if not hasattr(self, 'source'): # Can't initialize the collection until the source is known return exclusions = self.exclusionsCollection if exclusions is None: exclusions = (self, 'collectionExclusions') innerSource = (self, 'inclusions') if self.source is not None: innerSource = Union(self.source, innerSource) set = Difference(innerSource, exclusions) trash = self.trashCollection if trash is not None: set = Difference(set, trash) setattr(self, self.__collection__, set) def withoutTrash(self, copy=True): """ Pull out the non-trash part of AppCollection. Smart collections are 'special' - they almost always include the trash as a part of their structure on the _right side of their Difference set. This means that when they are hooked into a larger collection tree, they need to only give out the _left side, which has no trash. """ if self.trash is schema.ns('osaf.pim', self.itsView).trashCollection: left = getattr(self, self.__collection__)._left if copy: return left.copy(self.itsUUID) return left return self
class WrapperCollection(ContentCollection): """ A class for collections wrapping other collections """ __metaclass__ = schema.CollectionClass __collection__ = 'set' set = schema.One(schema.TypeReference('//Schema/Core/AbstractSet')) sources = schema.Sequence(inverse=ContentCollection.sourceFor, doc="the collections being wrapped", initialValue=[]) # When a source is removed and the resulting set is an EmptySet # this collection gets deleted if autoDelete is True. autoDelete = schema.One(schema.Boolean, defaultValue=False) schema.addClouds(copying=schema.Cloud(byCloud=[sources])) def __setup__(self): self._sourcesChanged_('add') self.watchCollection(self, 'sources', '_sourcesChanged') def _sourcesChanged(self, op, item, attribute, sourceId, dirties): if op in ('add', 'remove'): view = self.itsView source = view[sourceId] name = source.__collection__ if op == 'add': set = self._sourcesChanged_(op) sourceChanged = set.sourceChanged actualSource = set.findSource(sourceId) for uuid in source.withoutTrash(False).iterkeys(): view._notifyChange(sourceChanged, 'add', 'collection', source, name, False, uuid, dirties, actualSource) elif op == 'remove': set = getattr(self, self.__collection__) sourceChanged = set.sourceChanged actualSource = set.findSource(sourceId) for uuid in source.withoutTrash(False).iterkeys(): view._notifyChange(sourceChanged, 'remove', 'collection', source, name, False, uuid, dirties, actualSource) set = self._sourcesChanged_(op) if self.autoDelete and type(set) is EmptySet: self.delete() def addSource(self, source): if source not in self.sources: self.sources.append(source) def removeSource(self, source): if source in self.sources: self.sources.remove(source)
class AbstractCollection(items.ContentItem): """ """ schema.kindInfo( displayName="AbstractCollection" ) indexName = schema.One(schema.String, initialValue="__adhoc__") renameable = schema.One(schema.Boolean) invitees = schema.Sequence( "osaf.pim.mail.EmailAddress", doc="The people who are being invited to share in this item; filled " "in when the user types in the DV's 'invite' box, then cleared on " "send (entries copied to the share object).\n\n" "Issue: Bad that we have just one of these per item collection, " "though an item collection could have multiple shares post-0.5", otherName="inviteeOf", # can't use inverse here while ItemCollection lives! initialValue=() ) # redirections about = schema.Role(redirectTo="displayName") schema.addClouds( copying = schema.Cloud( invitees, byCloud=[items.ContentItem.contentsOwner] ), sharing = schema.Cloud( none = ["displayName"] ), ) rep = schema.One(schema.TypeReference('//Schema/Core/AbstractSet'), initialValue=None) subscribers = schema.Sequence(initialValue=[]) def __init__(self, *args, **kw): super(AbstractCollection, self).__init__(*args, **kw) self.subscribers = [] def collectionChanged(self, op, item, name, other, *args): if op: # use mapChanges to propagate any updates (not add/removes) that # happened since the last self.itsView.mapChanges(mapChangesCallable, True) self.notifySubscribers(op, item, name, other, *args) def notifySubscribers(self, op, item, name, other, *args): for i in self.subscribers: method_name = getattr(i, "collectionEventHandler") method = getattr(i, method_name) method(op, item, name, other, *args) def contentsUpdated(self, item): pass def __iter__(self): for i in self.rep: yield i def size(self): return len(self.rep) # index delegates def addIndex(self, indexName, indexType, **kwds): return self.rep.addIndex(indexName, indexType, **kwds) def removeIndex(self, indexName): return self.rep.removeIndex(indexName) def setIndexDescending(self, indexName, descending=True): return self.rep.setDescending(indexName, descending) def getByIndex(self, indexName, position): return self.rep.getByIndex(indexName, position) def removeByIndex(self, indexName, position): return self.rep.removeByIndex(indexName, position) def insertByIndex(self, indexName, position, item): return self.rep.insertByIndex(indexName, position, item) def replaceByIndex(self, indexName, position, with): return self.rep.replaceByIndex(indexName, position, with) def placeInIndex(self, item, after, *indexNames): return self.rep.placeInIndex(item, after, indexNames) def iterindexkeys(self, indexName): for key in self.rep.iterindexkeys(indexName): yield key def iterindexvalues(self, indexName): for value in self.rep.iterindexvalues(indexName): yield value def iterindexitems(self, indexName): for pair in self.rep.iterindexitems(indexName): yield pair def getIndexEntryValue(self, indexName, item): return self.rep.getIndexEntryValue(indexName, item) def setIndexEntryValue(self, indexName, item, value): return self.rep.setIndexEntryValue(indexName, item, value) def getIndexPosition(self, indexName, item): return self.rep.getIndexPosition(indexName, item) def firstInIndex(self, indexName): return self.rep.firstInIndex(indexName) def lastInIndex(self, indexName): return self.rep.lastInIndex(indexName) def nextInIndex(self, previous, indexName): return self.rep.nextInIndex(previous, indexName) def previousInIndex(self, next, indexName): return self.rep.previousInIndex(next, indexName)
class KindParameterizedEvent(BlockEvent): kindParameter = schema.One( schema.TypeReference('//Schema/Core/Kind'), required=True, ) schema.addClouds(copying=schema.Cloud(byRef=[kindParameter]))