class Model: def __init__(self): self.myMoney = Observable(0) def addMoney(self, value): self.myMoney.set(self.myMoney.get() + value) def removeMoney(self, value): self.myMoney.set(self.myMoney.get() - value)
class diskMounterBaseClass(object): mounted = mountedModel() def __init__(self): self.log = logging.getLogger("vmDiskAbstract.diskMounterBaseClass") # 1 Unknown # 2 Mounted (is it mounted) # 4 Released # 8 Can be mounted # 16 and higher are implementation specific self.state = Observable(0) def updateFromMtab(self, ByTarget, BySource): # self.log.debug("ByTarget=%s" % (ByTarget)) # self.log.debug("BySource=%s" % (BySource)) mounted = True if not hasattr(self, "target"): mounted = False if not self.target in ByTarget.keys(): print "no match", ByTarget.keys(), self.target mounted = False oldState = self.state.get() currentState = oldState # self.log.info("statechange=%s->%s" % (oldState,currentState)) if ((currentState & 2) == 2) and (not mounted): currentState -= 2 if ((currentState & 2) == 0) and (mounted): currentState += 2 if currentState != oldState: # self.log.info("statechange=%s->%s" % (oldState,currentState)) self.state.set(currentState) return currentState def mount(self): currentState = self.state.get() if (currentState & 2) == 2: return currentState if (currentState & 2) == 0: currentState += 2 self.state.set(currentState) return currentState def release(self): currentState = self.state.get() if (currentState & 2) == 0: return currentState if (currentState & 2) == 2: currentState -= 2 self.state.set(currentState) return currentState
class archiveInstance(object): def __init__(self): self.Name = Observable(None) self.FullPath = Observable(None) self.Magic = Observable(None) self.Format = Observable(None) self.Directory = Observable(None) self.Magic.addCallback(self,self.OnMagic) def update(self,source): self.Name.update(source.Name.get()) self.FullPath.update(source.FullPath.get()) self.Magic.update(source.Magic.get()) self.Format.update(source.Format.get()) self.Directory.update(source.Directory.get()) def OnMagic(self): format = None magicString = self.Magic.get() cleanstring = str.strip(magicString) splitLine = cleanstring.split(', ') if splitLine[0] in formatMap.keys(): format = formatMap[splitLine[0]] self.Format.update(format)
class archiveCollection(object): def __init__(self): self.path = Observable(None) self.archives = ObservableDict() def addArchive(self,archiveAdd): if not isinstance(archiveAdd,archiveInstance): return CollectionKeys = set(self.archives.keys()) newCollection = archiveInstance() newName = archiveAdd.Name.get() if newName in CollectionKeys: return self.archives[newName] newCollection.update(archiveAdd) newCollection.Directory.update(self.path.get()) self.archives[newName] = newCollection return self.archives[newName]
class Model: def __init__(self): log = logging.getLogger(f"c.{__name__}.init") log.info("Start init") self.xpoint = 200 self.ypoint = 200 self.expX = Observable(1) self.expY = Observable(1) self.funcType = Observable("cos") self.res = Observable(None) def calculate(self): log = logging.getLogger(f"c.{__name__}.calculate") log.info("Calculating") x, y = np.meshgrid(np.linspace(-5, 5, self.xpoint), np.linspace(-5, 5, self.ypoint)) if self.funcType.get() == "cos": z = np.cos(x**self.expX.get() * y**self.expY.get()) elif self.funcType.get() == "sin": z = np.sin(x**self.expX.get() * y**self.expY.get()) else: log.error(f"Unrecognized funcType {self.funcType.get()}") self.res.set({"x": x, "y": y, "z": z}) def setExpX(self, value): log = logging.getLogger(f"c.{__name__}.setExpX") log.info(f"Setting exp X to {value}") self.expX.set(value) self.calculate() def setExpY(self, value): log = logging.getLogger(f"c.{__name__}.setExpY") log.info(f"Setting exp Y to {value}") self.expY.set(value) self.calculate() def setFunc(self, funcType): log = logging.getLogger(f"c.{__name__}.setFunc") log.info(f"Setting funcType to {funcType}") self.funcType.set(funcType) self.calculate()
class ForcePlates(object): def __init__(self): self.forces = Observable([0, 0, 0, 0]) self.forces_after_calibration = Observable([0, 0, 0, 0]) self.calibrations = False def init_calibs(self): """ This method is used to inject the calibration dependency. Ex. >>> from Affine import Affine >>> f().initCalibrations(Affine) """ self.calibrations = [Affine() for i in range(4)] def init_labpro(self, labpro): """ Initializes the LabPro state. """ labpro.Open() labpro.SetNumChannelsAndModes(c_int32(4), c_int16(1), c_int16(0)) def uninit_labpro(self, labpro): """ Uninitializes the LabPro state. """ self.blink(labpro) labpro.Close() def update(self, labpro): """ Updates the force measurements from the LabPro. """ data = lpuu.read_and_interpret(labpro) if data is not None: self.forces.set(data[:4]) if self.calibrations: fac = self.forces_with_calibs() self.forces_after_calibration.set(fac) def forces_with_calibs(self): """ Applies calibrations to the forces and returns the new array. """ f = self.forces.get() return [self.calibrations[i].process(f[i]) for i in range(4)] def set_all_zero(self): """ Sets all sensors to zero. """ for cal in self.calibrations: cal.setZeroLast() def blink(self, labpro): """ Kills program. """ lpuu.send_string(labpro, 's') def send_program(self, labpro, file): """ Sends a program line-by-line to the LabPro. Since the format s{ ... } is required for all instructions, it is added here automatically. The instruction manual has more information for the specification of such programs. """ for line in file.readlines(): lpuu.send_string(labpro, "s{%s}\n" % line.strip('\n'))
class Model: """Provides fields and logic for a tennnis match.""" def __init__(self): self.noEndingTiebreak = Observable(False) self.doublesMatch = Observable(False) self.mensMatch = Observable(0) self.team = Observable(["",""]) self.gameScore = Observable([0,0]) self.setScore = Observable([[0,0],[0,0],[0,0],[0,0],[0,0]]) self.currentSet = Observable(0) self.matchScore = Observable([0,0]) self.tiebreak = Observable(False) self.message = Observable("") self.server = Observable(-1) self.winner = Observable(-1) self.numberOfSets = Observable(0) self.matchOver = False self.winSets = Observable(0) self.matchType = Observable("") self.tiebreakToWin = 7 # Default for normal tiebreak game. Will set to 10 for end tiebreak in doubles match. self.matchPointCount = [0,0] self.setPointCount = [0,0] self.breakPointCount = [0,0] self.deuceCount = 0 self.duplicateLastName = set([]) self.specialCaseNames = [] self.teamScoreNames = ["",""] def setNoEndingTiebreak(self,bool): """ Establishes whether final set can end with a tiebreak. @type bool: boolean @param bool: Flag, True if final set can NOT end with a tiebreak. """ self.noEndingTiebreak.set(bool) def setDoublesMatch(self,bool): """ Establishes whether this is a doubles or singles match. @type bool: boolean @param bool: Flag, True if this is a doubles match. """ self.doublesMatch.set(bool) def setMensMatch(self,i): """ Establishes whether this is a men's match, women's, or some form of doubles. Sets that field, also the corresponding number of sets. @type i: integer @param i: Flag, 0 = women's match, 1 = men's match, 2 = mixed doubles match. """ self.mensMatch.set(i) if (self.mensMatch.get() == 1): self.numberOfSets.set(5) # I'm assuming 5 sets for men. This may not always be the case, in which case I'll include in dialog. self.winSets.set(3) # I'm assuming 5 sets for men. This may not always be the case, in which case I'll include in dialog. else: # either womens, or some form of doubles. All have three sets. self.numberOfSets.set(3) self.winSets.set(2) def changeServer(self): """ Swaps the serving team / player. """ # Simple toggle of server team if self.server.get() == 0: self.server.set(1) else: self.server.set(0) def gameDelta(self): """ Returns the absolute value of the difference in the current game score. @rtype: integer @return: The absolute value of the difference in the current game score. """ return scoreDelta(self.gameScore.get()) def gamePoint(self, score): """ Returns whether the current score corresponds to game point. @type score: list of integers @param score: The current game score. @rtype: boolean @return: Whether the current score corresponds to game point. """ gameLeader = leader(score) if gameLeader == -1: # Game is tied return False # All below, game is not tied. # Tiebreak game if (self.tiebreak.get()) and (score[gameLeader] > self.tiebreakToWin - 2): return True # Regular game if (not self.tiebreak.get()) and (score[gameLeader] > 2): return True return False def breakPoint(self,score): """ Returns whether the current score corresponds to break point. @type score: list of integers @param score: The current game score. @rtype: boolean @return: Whether the current score corresponds to break point. """ if not self.gamePoint(score): return False # All below, it is game point util.dbgprint(DEBUG, "Break point: it is Game point") # No break point in tiebreak game if self.tiebreak.get(): return False gameLeader = leader(score) if gameLeader == -1: # I think this cannot be true if get here. Maybe remove this. return False util.dbgprint(DEBUG, "breakPoint: Game leader is {}\tServer is: {}\tSet score is: {}\tGame score is: {}".format( self.team[gameLeader],self.server.get(),self.setScore.get(),score)) if gameLeader != self.server.get(): # Game point, and receiver is ahead, so it's break point return True def setPoint(self,score): """ Returns whether the current score corresponds to set point. @type score: list of integers @param score: The current game score. @rtype: boolean @return: Whether the current score corresponds to set point. """ if not self.gamePoint(score): return False # All below, it is game point util.dbgprint(DEBUG, "Set point: it is Game point") setScore = self.setScore.get()[self.currentSet.get()] setLeader = leader(setScore) gameLeader = leader(score) util.dbgprint(DEBUG, "setPoint: Set leader is {}\tSet score is: {}\tGame score is: {}".format( self.team[setLeader],self.setScore.get(),score)) # Tiebreak game if self.tiebreak.get(): util.dbgprint(DEBUG, "Tiebreak set point") return True # Regular game, set not tied, set leader is winning game, and their set score is 5 or higher if setLeader != -1 and setLeader == gameLeader and setScore[setLeader] > 4: return True def matchPoint(self,score): """ Returns whether the current score corresponds to match point. @type score: list of integers @param score: The current game score. @rtype: boolean @return: Whether the current score corresponds to match point. """ if not self.setPoint(score): return False # All below, it is set point util.dbgprint(DEBUG, "Match point: it is Set point") setScore = self.setScore.get()[self.currentSet.get()] gameLeader = leader(score) setLeader = leader(setScore) # Doubles match if self.doublesMatch.get() and self.currentSet.get() + 1 == self.numberOfSets.get(): util.dbgprint(DEBUG, "Doubles match, last set, so match point.") return True # Singles match util.dbgprint(DEBUG, "matchPoint: Set leader is {}\tMatch score is: {}\tSet score is: {}\tGame score is: {}".format( self.team[setLeader],self.matchScore.get(),self.setScore.get(),score)) # Set point, and game leader match score (# of serts) is one less than tat needed to win if self.matchScore.get()[gameLeader] == self.winSets.get() - 1: return True def messageCheck(self, score): """ Determines what message should be displayed on scoreboard based on match conditions. @type score: list of integers @param score: The current game score. """ # Note: Prefix is way overdone vs. what you see on TV matches. But, it's accurate, and I like it. prefix = ("","","Double ","Triple ","Quadruple ","Quintuple ","Sextuple ") # Default message is just the match type (e.g. Semifinal) # Might want to revise this so default message is more random, e.g. nothing most of time, match type maybe 25% of time. Later version. msg = self.matchType.get() # Deuce if (not self.tiebreak.get()) and (score[0] == 3) and (score[1] == 3): self.deuceCount += 1 if self.deuceCount <= 1: # First deuce self.message.set("Deuce") else: self.message.set("Deuce #{}".format(self.deuceCount)) # Subsequent deuces return if self.tiebreak.get(): msg = "Tiebreak" delta = scoreDelta(score) matchScore = self.matchScore.get() matchLeader = leader(matchScore) gameLeader = leader(score) # Someone has won the match if matchLeader != -1 and matchScore[matchLeader] == self.winSets.get(): self.message.set(str(self.team[matchLeader])+" won " + self.matchOrChampionship() +"!") return # Game point, check various possibilities if self.gamePoint(score): # if get to here, someone is ahead, so gameLeader in {0,1} if self.matchPoint(score): # It is match point self.matchPointCount[gameLeader] += 1 if self.matchType.get() == "Championship": # It is match piont in a championship s = "Championship Point" else: s = "Match Point" # It is match point in a regular game, i.e. not championship # First match point, show prefix (e.g. Double) along with match point message if(self.matchPointCount[gameLeader] <= 1): self.message.set(prefix[delta]+s) else: # Subsequent match point, show match point message along with count for leading player self.message.set(s+" #{}".format(self.matchPointCount[gameLeader])) return if self.setPoint(score): self.setPointCount[gameLeader] += 1 # First set point, show prefix (e.g. Double) along with set point message if(self.setPointCount[gameLeader] <= 1): self.message.set(prefix[delta]+"Set Point") else: # Subsequent set point, show set point message along with count for leading player self.message.set("Set Point #{}".format(self.setPointCount[gameLeader])) return if self.breakPoint(score): self.breakPointCount[gameLeader] += 1 # First break point, show prefix (e.g. Double) along with break point message if(self.breakPointCount[gameLeader] <= 1): self.message.set(prefix[delta]+"Break Point") else: # Subsequent break point, show break point message along with count for leading player self.message.set("Break Point #{}".format(self.breakPointCount[gameLeader])) return self.message.set(msg) def matchOrChampionship(self): """ Returns whether this is a regular match or a championship. @rtype: string @return: Either 'championship' or 'match' """ if self.matchType.get().lower() == 'championship': return 'championship' else: return 'match' def incrementGameScore(self,team): """ Increments the current game score, and handles game logic (win, deuce, tiebreak, and message) @type team: integer @param team: The team that scored the point. """ if not self.matchOver: #self.message.set("") s = self.gameScore.get() # Increment score of the team that scored s[team] += 1 util.dbgprint(DEBUG, str(self.team[team])+" " + self.singular('score') + ". Score = "+str(s)+". tiebreak is "+ str(self.tiebreak.get())) if not self.tiebreak.get(): # NOT a tiebreak # Point was Ad, and non-leading player scored, so back to Deuce if (s[0]==4) and (s[1]==4): s = [3,3] # Scoring player just won the game if (s[team] > 3) and (scoreDelta(s) > 1): util.dbgprint(DEBUG, str(self.team[team])+" "+self.singular('win')+" game.") # Reset for next game s = [0,0] self.breakPointCount = [0,0] self.deuceCount = 0 # Increment the game winning team's set score self.incrementSetScore(team) if not self.matchOver: self.changeServer() else: # tiebreak if (s[0]+s[1]) % 2 == 1: # It's an odd game, so change server. self.changeServer() if (s[team] >=self.tiebreakToWin) and (scoreDelta(s) > 1): # Point winning team just won the tiebreak util.dbgprint(DEBUG, str(self.team[team])+" "+self.singular('win')+" tiebreak game.") # Increment the game winning team's set score self.incrementSetScore(team) # Increment the game winning team's match score, as they also won the set. self.incrementMatchScore(team) # Reset for next game s = [0,0] self.breakPointCount = [0,0] self.deuceCount = 0 self.tiebreak.set(False) # Reset for next game self.gameScore.set(s) util.dbgprint(DEBUG, "Before message check. Score: {}\tSets:\t{}".format(s,self.setScore.get())) # Determine what message should be displayed self.messageCheck(s) def incrementSetScore(self,team): """ Increments the current set score (# of games), and handles set logic (win, update match score) @type team: integer @param team: The team that won the game. """ s = self.setScore.get() thisSet = self.currentSet.get() # Increment game winning team's set score s[thisSet][team] += 1 self.setScore.set(s) util.dbgprint(DEBUG, "Set: " + str(self.currentSet.get()) + ". Current set score: "+str(s)) if not self.tiebreak.get(): # NOT a tiebreak if (s[thisSet][team] > 5) and (scoreDelta(s[thisSet]) > 1): # Game winners also won the current set util.dbgprint(DEBUG, str(self.team[team]) +" "+self.singular('win')+ " set "+str(thisSet)) # Reset for next set self.setPointCount = [0,0] self.incrementMatchScore(team) if not self.doublesMatch.get() and self.noEndingTiebreak.get() and (thisSet == self.numberOfSets.get()-1): # Singles match, and no ending tiebreak, and we're in the final set, so just keep playing games until match winner. return if (s[thisSet][0]==6) and (s[thisSet][1]==6): # We now enter into a tiebreak self.tiebreak.set(True) self.gameScore.set([0,0]) util.dbgprint(DEBUG, "Now entering tiebreak") def incrementMatchScore(self,team): """ Increments the match score (# of sets), and handles match logic (win) @type team: integer @param team: The team that won the set. """ currSet = self.currentSet.get() + 1 s = self.matchScore.get() # Increment the set winning team's match score s[team] += 1 self.matchScore.set(s) util.dbgprint(DEBUG, "Match score in set "+ str(currSet) + " is "+str(s)) util.dbgprint(DEBUG, "team # is "+str(team)+", with "+str(s[team])+" sets.") if s[team] == self.winSets.get(): # The set winning team has won the match self.winner.set(team) util.dbgprint(DEBUG, str(self.team[team])+" "+self.singular('win')+" " + self.matchOrChampionship() + "!") self.matchOver = True self.server.set(-1) currSet -= 1 return if self.doublesMatch.get() and self.numberOfSets.get() == 2: # It is a doubles match, with two sets if s == [1,1]: # Each team has won a set, so now it is the final 10-point tiebreak self.tiebreak.set(True) self.tiebreakToWin = 10 # Update the set number self.currentSet.set(currSet) def singular(self,word): """ Returns plural or singular form for word depending on singles or doubles match. Not an intelligent pluralization. @type word: string @param word: Word to be returned, singular or plural. @rtype: string @return: Plural or singular form for word depending on singles or doubles match. Not an intelligent pluralization. """ # This is crude. It works for the words that I am using. Probably could be more sophisticated. Later version. if self.doublesMatch.get(): return word else: return word+'s'
class Model: def __init__(self): logg = logging.getLogger(f"c.{__class__.__name__}.init") logg.info("Start init") self._out_fold_not_set = "Not set not known" self.output_folder = Observable(self._out_fold_not_set) self.input_folders = Observable({}) # dict to send photo_info -> only active photos self.photo_info_list_active = Observable({}) # remember all PhotoInfo loaded self._photo_info_list_all = {} # dict to send (selection_info, status) -> keep in de-selected one self.selection_list = Observable({}) # full path of the current_photo_prim self.current_photo_prim = Observable("") # index in self._active_photo_list of current photo self._index_prim = 0 # full path of the current_photo_echo self.current_photo_echo = Observable("") self._index_echo = 0 # set of valid photo extensions to load in _active_photo_list self._is_photo_ext = set((".jpg", ".jpeg", ".JPG", ".png")) # thumbnail size for ThumbButtons self._thumb_size = 50 # how much to move the image from keyboard self._mov_delta = 200 # ImageTk that holds the cropped picture self.cropped_prim = Observable(None) self.cropped_echo = Observable(None) # cache dimension for the Holder self._cropper_cache_dim = 10 self._loaded_croppers = Holder(self._cropper_cache_dim) self._widget_wid = -1 self._widget_hei = -1 # dict of metadata {name:value} self.metadata_prim = Observable({}) self.metadata_echo = Observable(None) self._load_named_metadata() # setup layout info self._layout_tot = 6 self._layout_is_double = (1, 5) self.layout_current = Observable(0) self._old_single_layout = 0 # double layout to jump to when swapDoubleLayout is called self._basic_double_layout = 1 def setOutputFolder(self, output_folder_full): logg = logging.getLogger(f"c.{__class__.__name__}.setOutputFolder") logg.info(f"Setting output_folder to '{output_folder_full}'") logui = logging.getLogger("UI") logui.info(f"Setting output folder to '{output_folder_full}'") # create the folder if it doesn't exist if not isdir(output_folder_full): logui.info(f"Not a folder '{output_folder_full}', creating it") makedirs(output_folder_full) self.output_folder.set(output_folder_full) def addInputFolder(self, input_folder_full): logg = logging.getLogger(f"c.{__class__.__name__}.addInputFolder") logg.info(f"Adding new input_folder '{input_folder_full}'") logui = logging.getLogger("UI") # check for folder existence if not isdir(input_folder_full): logui.error(f"Not a valid folder: {input_folder_full}") return 1 logui.info(f"Selected new input folder: '{input_folder_full}'") old_folders = self.input_folders.get() if input_folder_full in old_folders: logui.warn("Selected folder already in input list.") return 0 old_folders[input_folder_full] = True self.input_folders.set(old_folders) self.updatePhotoInfoList() def toggleInputFolder(self, state): logg = logging.getLogger(f"c.{__class__.__name__}.toggleInputFolder") # logg.setLevel("TRACE") logg.info("Setting new input_folder state") state = {x: state[x].get() for x in state} logg.trace(f"state {state}") if sum((state[x] for x in state)) > 0: # at least one still toggled self.input_folders.set(state) else: # no folders toggled, revert to previous state logui = logging.getLogger("UI") logui.warn("At least one input folder has to be selected.") self.input_folders._docallbacks() self.updatePhotoInfoList() def saveSelection(self): logg = logging.getLogger(f"c.{__class__.__name__}.saveSelection") # logg.setLevel("TRACE") logg.info("Saving selected pics") logui = logging.getLogger("UI") # check that output_folder is set output_folder = self.output_folder.get() if output_folder == self._out_fold_not_set: logui.warn( "Set the output folder before saving the selection list") return # get current selection_list, {pic: [PhotoInfo, is_selected] } selection_list = self.selection_list.get() logg.trace(selection_list) # keep only selected active_selection = tuple(p for p in selection_list if selection_list[p][1]) if len(active_selection) == 0: logui.warn("No active pic in selection list") return elif len(active_selection) == 1: s = "" else: s = "s" logui.info(f"Saving {len(active_selection)} pic{s} in {output_folder}") out_fol_content = set(listdir(output_folder)) for pic in active_selection: base_pic = basename(pic) if base_pic in out_fol_content: logui.warn(f"{base_pic} already in output folder, skipping it") # MAYBE copy it with changed name else: copy2(pic, output_folder) def setLayout(self, lay_num): logg = logging.getLogger(f"c.{__class__.__name__}.setLayout") logg.info(f"Setting layout_current to '{lay_num}'") self.layout_current.set(lay_num) # if the new layout is not double, reset the values for meta_echo if not self.layout_current.get() in self._layout_is_double: self.metadata_echo.set(None) def cycleLayout(self): logg = logging.getLogger(f"c.{__class__.__name__}.cycleLayout") # logg.setLevel("TRACE") logg.info("Cycling layout") old_layout = self.layout_current.get() new_layout = (old_layout + 1) % self._layout_tot self.setLayout(new_layout) # if the new layout is double and the old is not, sync echo and prim indexes if new_layout in self._layout_is_double and ( old_layout not in self._layout_is_double): self.moveIndexEcho("sync") def swapDoubleLayout(self): """Go from a single to a double layout and back Save the current one if it's single, and go back to that """ logg = logging.getLogger(f"c.{__class__.__name__}.swapDoubleLayout") # logg.setLevel("TRACE") logg.info("Swapping layout") # the layout is double: go back to saved single layout if self.layout_current.get() in self._layout_is_double: self.setLayout(self._old_single_layout) # the layout is single: save it and go to double else: self._old_single_layout = self.layout_current.get() self.setLayout(self._basic_double_layout) # also sync echo to prim self.moveIndexEcho("sync") def updatePhotoInfoList(self): """Update photo_info_list_active, load new photos and relative info photo_info_list_active is actually a dict of PhotoInfo objects Has to load the thumbnail and metadata """ logg = logging.getLogger(f"c.{__class__.__name__}.updatePhotoInfoList") # logg.setLevel("TRACE") logg.info("Update photo_info_list_active") # list of filenames of active photos: ideally parallel to # photo_info_list_active.keys() but dict order can't be trusted so we # keep track here of the index # TODO the list passed to view to sort in special way # TODO sort list according to metadata # hopefully loading them will be fast, all will be needed to sort self._active_photo_list = [] input_folders = self.input_folders.get() for folder in input_folders: # the folder is not toggled, skip it if input_folders[folder] is False: continue for photo in listdir(folder): photo_full = join(folder, photo) if self._is_photo(photo_full): self._active_photo_list.append(photo_full) new_photo_info_active = {} for photo_full in self._active_photo_list: # load new photos in _photo_info_list_all if photo_full not in self._photo_info_list_all: self._photo_info_list_all[photo_full] = PhotoInfo( photo_full, self._thumb_size) # collect the active PhotoInfo object in the new dict new_photo_info_active[photo_full] = self._photo_info_list_all[ photo_full] logg.info( f"photo_info_list_active has now {len(new_photo_info_active)} items" ) logui = logging.getLogger("UI") logui.info( f"There are now {len(new_photo_info_active)} images in the active list." ) self.photo_info_list_active.set(new_photo_info_active) current_photo_prim = self.current_photo_prim.get() if current_photo_prim in self._active_photo_list: # the photo is still in list: if needed update the index self._index_prim = self._active_photo_list.index( current_photo_prim) else: # the photo is not in list anymore: reset to 0 self._index_prim = 0 self._update_photo_prim(self._active_photo_list[0]) # reset the echo index to follow prim self._index_echo = self._index_prim # reload the echo image self._update_photo_echo(self._active_photo_list[self._index_echo]) def _is_photo(self, photo_full): _, photo_ext = splitext(photo_full) return photo_ext in self._is_photo_ext def setIndexPrim(self, index_prim): logg = logging.getLogger(f"c.{__class__.__name__}.setIndexPrim") logg.info(f"Setting index_prim to {index_prim}") self._index_prim = index_prim self._update_photo_prim(self._active_photo_list[index_prim]) def moveIndexPrim(self, direction): logg = logging.getLogger(f"c.{__class__.__name__}.moveIndexPrim") logg.info(f"Moving index prim {direction}") if direction == "forward": new_index_prim = self._index_prim + 1 elif direction == "backward": new_index_prim = self._index_prim - 1 new_index_prim = new_index_prim % len( self.photo_info_list_active.get()) self.setIndexPrim(new_index_prim) def seekIndexPrim(self, pic): logg = logging.getLogger(f"c.{__class__.__name__}.seekIndexPrim") logg.info("Seeking index prim") # MAYBE the pic is not in _active_photo_list... very weird, add guards? self._index_prim = self._active_photo_list.index(pic) self._update_photo_prim(pic) def _update_photo_prim(self, pic_prim): """Change what is needed for a new pic_prim - current_photo_prim - cropped_prim - prim metadata """ logg = logging.getLogger(f"c.{__class__.__name__}._update_photo_prim") # logg.setLevel("TRACE") logg.info(f"Updating photo prim, index {self._index_prim}") self.current_photo_prim.set(pic_prim) # resets zoom level and pos for the new photo; can only be done AFTER # mainloop starts, during initialization Model.doResize has not been # called yet, and the widget dimensions are still undefined; # the first time reset_image will be called by the Configure event later if self._widget_wid != -1: crop_prim = self._loaded_croppers.get_cropper(pic_prim) crop_prim.reset_image(self._widget_wid, self._widget_hei) self.cropped_prim.set(crop_prim.image_res) # if the layout is double, copy the new zoom level to echo pic if self.layout_current.get() in self._layout_is_double: self._cloneParams() # get the metadata for the image metadata_exif_prim = self._photo_info_list_all[pic_prim].get_metadata() metadata_named_prim = self._parse_metadata(metadata_exif_prim) self.metadata_prim.set(metadata_named_prim) def setIndexEcho(self, index_echo): logg = logging.getLogger(f"c.{__class__.__name__}.setIndexEcho") logg.info(f"Setting index_echo to {index_echo}") self._index_echo = index_echo self._update_photo_echo(self._active_photo_list[index_echo]) def moveIndexEcho(self, direction): logg = logging.getLogger(f"c.{__class__.__name__}.moveIndexEcho") logg.info(f"Moving index echo {direction}") if not self.layout_current.get() in self._layout_is_double: # TODO move index prim in this case logg.warn("Current layout is not double, can't move index echo") return if direction == "forward": new_index_echo = self._index_echo + 1 elif direction == "backward": new_index_echo = self._index_echo - 1 elif direction == "sync": new_index_echo = self._index_prim new_index_echo = new_index_echo % len( self.photo_info_list_active.get()) self.setIndexEcho(new_index_echo) def _update_photo_echo(self, pic_echo): """Change what is needed for a new pic_echo in echo frame - current_photo_echo - cropped_echo If _index_echo == _index_prim do not recompute image crop """ logg = logging.getLogger(f"c.{__class__.__name__}._update_photo_echo") logg.info(f"Updating photo echo, index {self._index_echo}") self.current_photo_echo.set(pic_echo) if self._widget_wid != -1: self._cloneParams() if self.layout_current.get() in self._layout_is_double: # get the metadata for the image metadata_exif_echo = self._photo_info_list_all[ pic_echo].get_metadata() metadata_named_echo = self._parse_metadata(metadata_exif_echo) self.metadata_echo.set(metadata_named_echo) else: self.metadata_echo.set(None) def likePressed(self, which_frame): """Update selection_list accordingly""" logg = logging.getLogger(f"c.{__class__.__name__}.likePressed") # logg.setLevel("TRACE") logg.info(f"Like pressed on {which_frame}") # if the layout is not double consider the event from prim cur_lay_is_double = self.layout_current.get() in self._layout_is_double if (not cur_lay_is_double) and which_frame == "echo": which_frame = "prim" if which_frame == "prim": new_pic = self.current_photo_prim.get() elif which_frame == "echo": new_pic = self.current_photo_echo.get() else: logg.error(f"Unrecognized frame {which_frame}") old_selection_list = self.selection_list.get() if new_pic in old_selection_list: # if it was in selection_list already, toggle is_selected old_selection_list[new_pic][1] = not old_selection_list[new_pic][1] else: # add to dict (PhotoInfo, is_selected) old_selection_list[new_pic] = [ self._photo_info_list_all[new_pic], True ] self.selection_list.set(old_selection_list) def toggleSelectionPic(self, pic): logg = logging.getLogger(f"c.{__class__.__name__}.toggleSelectionPic") # logg.setLevel("TRACE") logg.info(f"Toggling selection_list {pic}") old_selection_list = self.selection_list.get() old_selection_list[pic][1] = not old_selection_list[pic][1] self.selection_list.set(old_selection_list) def doResize(self, widget_wid, widget_hei): """Triggered by a configure event in the Label Also saves Label dimension, so that when the photo is changed, the new crop can be computed """ logg = logging.getLogger(f"c.{__class__.__name__}.doResize") # logg.setLevel("TRACE") logg.info("Do resize") self._widget_wid = widget_wid self._widget_hei = widget_hei # get the current_photo_prim full name pic_prim = self.current_photo_prim.get() # reset the image with the new widget dimension: # get the cropper for the image crop_prim = self._loaded_croppers.get_cropper(pic_prim) # reset the image zoom/pos crop_prim.reset_image(self._widget_wid, self._widget_hei) # update the Observable self.cropped_prim.set(crop_prim.image_res) if self.layout_current.get() in self._layout_is_double: # clone params to echo self._cloneParams() def zoomImage(self, direction, rel_x=-1, rel_y=-1): logg = logging.getLogger(f"c.{__class__.__name__}.zoomImage") # logg.setLevel("TRACE") logg.trace(f"Zooming in direction {direction}") # get current prim pic pic_prim = self.current_photo_prim.get() # get the cropper for the image crop_prim = self._loaded_croppers.get_cropper(pic_prim) # zoom the image crop_prim.zoom_image(direction, rel_x, rel_y) # update the Observable self.cropped_prim.set(crop_prim.image_res) if self.layout_current.get() in self._layout_is_double: self._cloneParams() def moveImageDirection(self, direction): """Move image in the specified direction of self._mov_delta""" logg = logging.getLogger(f"c.{__class__.__name__}.moveImageDirection") # logg.setLevel("TRACE") logg.trace(f"Moving in direction {direction}") if direction == "right": self._moveImage(self._mov_delta, 0) elif direction == "left": self._moveImage(-self._mov_delta, 0) elif direction == "up": self._moveImage(0, -self._mov_delta) elif direction == "down": self._moveImage(0, self._mov_delta) def moveImageMouse(self, mouse_x, mouse_y): """Move the image to follow the mouse""" logg = logging.getLogger(f"c.{__class__.__name__}.moveImageMouse") # logg.setLevel("TRACE") logg.trace("Moving mouse") delta_x = self._old_mouse_x - mouse_x delta_y = self._old_mouse_y - mouse_y self._old_mouse_x = mouse_x self._old_mouse_y = mouse_y self._moveImage(delta_x, delta_y) def saveMousePos(self, mouse_x, mouse_y): """Save the current mouse position""" self._old_mouse_x = mouse_x self._old_mouse_y = mouse_y def _moveImage(self, delta_x, delta_y): """Actually move image of specified delta""" logg = logging.getLogger(f"c.{__class__.__name__}._moveImage") # logg.setLevel("TRACE") logg.trace(f"Moving delta {delta_x} {delta_y}") # get current prim pic pic_prim = self.current_photo_prim.get() # get the cropper for the image crop_prim = self._loaded_croppers.get_cropper(pic_prim) # move the image crop_prim.move_image(delta_x, delta_y) # update the Observable self.cropped_prim.set(crop_prim.image_res) # if double, move echo as well if self.layout_current.get() in self._layout_is_double: self._cloneParams() def _cloneParams(self): """Clone current prim params to echo image""" # MAYBE the check for doubleness of the layout can be done here # cloning only makes sense if it's double after all logg = logging.getLogger(f"c.{__class__.__name__}._cloneParams") # logg.setLevel("TRACE") logg.trace("Cloning params") # get current prim pic pic_prim = self.current_photo_prim.get() # get the cropper for the image crop_prim = self._loaded_croppers.get_cropper(pic_prim) # get current echo pic pic_echo = self.current_photo_echo.get() # get the cropper for the image crop_echo = self._loaded_croppers.get_cropper(pic_echo) # get params from prim params = crop_prim.get_params() # copy them in echo crop_echo.load_params(params) # update echo observable self.cropped_echo.set(crop_echo.image_res) def _load_named_metadata(self): """Populate two dicts that map a readable name in the metadata field""" self.name2exif = {} self.name2exif["Date taken"] = "Image DateTime" # "EXIF DateTimeOriginal", # "EXIF DateTimeDigitized", self.name2exif["Exposure time"] = "EXIF ExposureTime" self.name2exif["Aperture"] = "EXIF FNumber" # self.name2exif["Program"] = "EXIF ExposureProgram" self.name2exif["ISO"] = "EXIF ISOSpeedRatings" self.name2exif["Width"] = "PILWidth" self.name2exif["Height"] = "PILHeight" def _parse_metadata(self, metadata_exif): """Parse raw EXIF metadata into named ones""" logg = logging.getLogger(f"c.{__class__.__name__}._parse_metadata") # logg.setLevel("TRACE") logg.info("Parsing metadata") metadata_named = {} # translate the names from EXIF to readable, set default values for name in self.name2exif: exif_name = self.name2exif[name] if exif_name in metadata_exif: if name == "Aperture": logg.trace(f"{str(metadata_exif[exif_name])}") fra = Fraction(str(metadata_exif[exif_name])) metadata_named[name] = fra.numerator / fra.denominator else: metadata_named[name] = metadata_exif[exif_name] else: metadata_named[name] = "-" logg.trace(f"{name}: {metadata_named[name]}") return metadata_named
class vmMdl: def __init__(self): # Three indexable ways self.libvirtName = Observable(None) self.libvirtId = Observable(None) self.libvirtUuid = Observable(None) #VIR_DOMAIN_NOSTATE= 0: no state #VIR_DOMAIN_RUNNING= 1: the domain is running #VIR_DOMAIN_BLOCKED= 2: the domain is blocked on resource #VIR_DOMAIN_PAUSED= 3: the domain is paused by user #VIR_DOMAIN_SHUTDOWN= 4: the domain is being shut down #VIR_DOMAIN_SHUTOFF= 5: the domain is shut off #VIR_DOMAIN_CRASHED= 6: the domain is crashed self.libvirtState = Observable(0) self.libvirtMem = Observable(None) self.libvirtMaxMem = Observable(None) self.libvirtNrVirtCpu = Observable(None) self.libvirtCpuTime = Observable(None) def assign(self,destination): destination.libvirtName.update(self.libvirtName.get()) destination.libvirtId.update(self.libvirtId.get()) destination.libvirtUuid.update(self.libvirtUuid.get()) destination.libvirtState.update(self.libvirtState.get()) destination.libvirtMem.update(self.libvirtMem.get()) destination.libvirtMaxMem.update(self.libvirtMaxMem.get()) destination.libvirtNrVirtCpu.update(self.libvirtNrVirtCpu.get()) destination.libvirtCpuTime.update(self.libvirtCpuTime.get()) def update(self,destination): libvirtName = self.libvirtName.get() if libvirtName != None: destination.libvirtName.update(libvirtName) #print libvirtName libvirtId = self.libvirtId.get() if libvirtId != None: destination.libvirtId.update(libvirtId) libvirtUuid = self.libvirtUuid.get() if libvirtUuid != None: destination.libvirtUuid.update(libvirtUuid) libvirtState = self.libvirtState.get() if libvirtState != None: destination.libvirtState.update(libvirtState) libvirtMaxMem = self.libvirtId.get() if libvirtMaxMem != None: destination.libvirtMaxMem.update(libvirtMaxMem) libvirtNrVirtCpu = self.libvirtNrVirtCpu.get() if libvirtNrVirtCpu != None: destination.libvirtNrVirtCpu.update(libvirtNrVirtCpu) libvirtCpuTime = self.libvirtCpuTime.get() if libvirtCpuTime != None: destination.libvirtCpuTime.update(libvirtCpuTime)