def UnregisterObserver( self, annoType, annoObserver ): "Input: Type annoType, BoardObserver annonObserve. remove the annObserver from the list of observers for annoType" logger.debug("Unregistering annotation %s observer %s" % (annoType, type(annoObserver))) annoHash = annoType.__name__ if annoHash in self.AnnoObservers: if annoObserver in self.AnnoObservers[annoHash]: self.AnnoObservers[annoHash].remove(annoObserver)
def isType( self, arg): "Input: either a classobj, or a list of classobjs. Return true if this class is one of the classobjs listed" if type(arg) == "list": clist = arg else: clist = [arg] if self.__class__ in clist: return True else: return False
def AddStroke( self, newStroke ): "Input: Stroke newStroke. Adds a Stroke to the board and calls any Stroke Observers as needed" logger.debug( "Adding Stroke: %d"% (newStroke.id) ) self.Strokes.append( newStroke ) logger.debug("Observers: %s" % (self.StrokeObservers)) for so in self.StrokeObservers: if newStroke not in self._removed_strokes: #Nobody has removed this stroke yet logger.debug("Alerting %s observer" % (type(so))) so.onStrokeAdded( newStroke )
def AnnotateStrokes(self, strokes, anno): "Input: list of Strokes strokes, Annotation anno. Add annotation to the set of strokes" logger.debug("Annotating strokes as %s" % (type(anno))) annoHash = type(anno) logger.debug("Annotype: %s, Observers: %s" % (annoHash, self.AnnoObservers.keys())) # time is used to play back stroke orderings during debug if not hasattr(anno, "Time"): anno.Time = datetime.datetime.utcnow() # the annotation keeps a list of strokes it annotates anno.Strokes = list(strokes) # for each stroke, add this annotation to the list of annotations of that type for that stroke for s in strokes: annoList = s.Annotations.setdefault(annoHash, []) annoList.append(anno) # if anyone listening for this class of anno, notify them annoObsvrs = self.AnnoObservers.get(annoHash) if (annoObsvrs != None): for i in annoObsvrs: if anno not in self._removed_annotations: #Will fail if someone has called "RemoveAnnotation" logger.debug("Alerting %s to new annotation %s" % (type(i), annoHash)) i.onAnnotationAdded(strokes, anno)
def RegisterForAnnotation( self, annoType, annoObserver, funcToCall = None ): "Input: Type annoType, BoardObserver annoObserver, function funcToCall. Call the observer when the matching annoation is found" # Function funcToCall Registers with the board an Observer based on an # Annotation type. Optional: An *additional* function to call on the observer upon annotation occurence annoHash = annoType.__name__ if ( annoHash not in self.AnnoObservers ): self.AnnoObservers[annoHash] = [] if annoObserver not in self.AnnoObservers[annoHash]: self.AnnoObservers[annoHash].append( annoObserver ) logger.debug( "Registering %s for %s"% ( type(annoObserver), annoHash ) ) else: logger.warning( "%s already registered for %s"% str(type(annoObserver)), str(annoHash) ) # TODO: can we depricate this? Is this still actively used? if (funcToCall != None): if not hasattr(annoObserver, "AnnoFuncs"): #Cause Python does things arguably backwards and calls the child ctors before the parents.. annoObserver.AnnoFuncs = {} if annoHash not in annoObserver.AnnoFuncs: annoObserver.AnnoFuncs[annoHash] = funcToCall logger.debug( "Registering %s.%s for %s" % (type(annoObserver),funcToCall, annoHash))
def UpdateAnnotation(self, anno, new_strokes=None, notify=True, remove_empty = True ): """Input: Annotation, Strokes. Changes the annotation and alerts the correct listeners. If the new strokes are empty, remove_empty determines whether to remove the annotation.""" # if strokes are different from the old strokes then we update them too # if notify is False, then don't send the update notification. This allows # people to perform multiple updates, and then call notify one time at the end # preventing everyone from being notified on every small change made logger.debug( "Updating Annotation: %s"% str(anno) ) # if we added or subtracted strokes, update accordingly annoHash = type(anno) old_strokes = anno.Strokes shouldRemove = (len(new_strokes) == 0 and remove_empty) #We still have strokes, or no strokes and we're not removing empty annos if new_strokes!=None: if new_strokes!=old_strokes: stroke_gone_list = list( set(old_strokes).difference(new_strokes)) stroke_added_list = list( set(new_strokes).difference(old_strokes)) for old_stroke in stroke_gone_list: if anno in old_stroke.Annotations[annoHash]: old_stroke.Annotations[annoHash].remove( anno ) for new_stroke in stroke_added_list: if annoHash in new_stroke.Annotations: new_stroke.Annotations[annoHash].append(anno) else: new_stroke.Annotations[annoHash] = [anno] anno.Strokes = new_strokes # if anyone is listening for this class of annotation, let them know we updated if not shouldRemove and notify and annoHash in self.AnnoObservers: for obs in self.AnnoObservers[annoHash]: # tell obs about the updated annotation if anno not in self._removed_annotations: #Fails if someone called removeAnnotation logger.debug("Alerting %s to updated annotation %s" % (type(i), annoHash)) obs.onAnnotationUpdated(anno) elif shouldRemove: #The strokes are empty, so remove the annotation self.RemoveAnnotation(anno)
def RemoveAnnotation(self, anno): "Input: Annotation anno. Removes anno from the board and alert the correct listeners" logger.debug( "Removing Annotation: %s"% str(anno) ) annoHash = type(anno) self._removed_annotations[anno] = True # if anyone is listening for this class of annotation, let them know if annoHash in self.AnnoObservers: for obs in self.AnnoObservers[annoHash]: obs.onAnnotationRemoved(anno) # remove the annotation from the strokes. # do this second, since observers may need to check the old strokes' properties for stroke in anno.Strokes: # logger.debug("RemoveAnnotation: stroke.Annotations = %s, id=%d", stroke.Annotations, stroke.id ) # logger.debug("RemoveAnnotation: anno.__class__ = %s", anno.__class__ ) try: stroke.Annotations[annoHash].remove(anno) #if len(stroke.Annotations[anno.__class__]) == 0: # del(stroke.Annotations[anno.__class__]) #Delete the entry if it's empty except KeyError: logger.error( "RemoveAnnotation: Annotation %s not found in stroke.Annotations"% annoHash ) except ValueError: logger.error( "RemoveAnnotation: Trying to remove nonexistant annotation %s"% anno )
def RegisterForStroke( self, strokeObserver ): "Input: BoardObserver stroke. Registers with the board an Observer to be called when strokes are added" logger.debug("Registering observer %s for STROKES" % (type(strokeObserver))) self.StrokeObservers.append( strokeObserver )
def AddBoardObserver ( self, obs ): "Input: Observer obs. Obs is added to the list of Board Observers" # FIXME? should we check that the object is one in the list once? logger.debug( "Adding Observer: %s" % str(type(obs)) ) self.BoardObservers.append( obs )
def UnregisterStrokeObserver( self, observer ): "Input: BoardObserver observer. Remove the observer from the list of stroke observers" logger.debug("Unregistering stroke observer %s" % (type(observer))) if observer in self.StrokeObservers: self.StrokeObservers.remove(observer)
def drawMyself( self ): # FIXME: does this tie us to the tk front-end? If so, what should # we put into the GUI API to enable this sort of animation? is it mostly # "update" that is needed? from SketchFramework import SketchGUI as gui canvas = gui.SketchGUISingleton() color_levels = { 0: "#FF6633", 1: "#FF00FF", 2: "#3366FF", 3: "#00CC00",} scale = 18 # pixels for text size # start with a list of all the annotations allAnnoSet = set([]) logger.debug("Watch set %s" % (self.watchSet)) for stroke in BoardSingleton().Strokes: logger.debug("Stroke annotations %s" % (stroke.findAnnotations(None))) for anno in stroke.findAnnotations(None): logger.debug ("%s in %s?" % (type(anno), list(self.watchSet))) if type(anno) in list(self.watchSet): logger.debug("Adding %s to annoset" % (anno)) allAnnoSet.add( anno ) # now make a map from the sets of strokes to the annotations on them annoMap = {} # dictionary of {frozenset(strokes):[annotations]} for anno in allAnnoSet: strokeset = frozenset(anno.Strokes) if strokeset not in annoMap: annoMap[strokeset] = [] annoMap[strokeset].append( anno ) # now assign a unique size for each anno on a given set of strokes sizeDict = {} for strokeset in annoMap.keys(): depth = 0 for anno in annoMap[strokeset]: sizeDict[anno] = depth depth += 1 # sort the annotations based on the time at which they were added annoList = list(allAnnoSet) annoList.sort(key= (lambda x: x.Time)) for anno in annoList: nestlevel = sizeDict[anno] # get the nesting level for this annotation # for the set of stroke this anno is annotating, find the bounding box tl = anno.Strokes[0].BoundTopLeft br = anno.Strokes[0].BoundBottomRight tlx = tl.X tly = tl.Y brx = br.X bry = br.Y bottomright_list = [s.BoundBottomRight for s in anno.Strokes] topleft_list = [s.BoundTopLeft for s in anno.Strokes] br, tl = _nestingBox(bottomright_list, topleft_list, scale = nestlevel*3) br.Y -= nestlevel * scale # save some space for text on bottom of box # if this is a "new" anno, wait a little before drawing if anno not in self.seenBefore: #time.sleep(0.5) self.seenBefore[anno] = True # now draw the actual boxes labeltext = anno.classname() tlx = tl.X tly = tl.Y brx = br.X bry = br.Y gui.drawBox(tl,br,color=color_levels[nestlevel % len(color_levels)]) gui.drawText(tl.X, br.Y+scale, size = 12, InText=labeltext)