예제 #1
0
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
예제 #3
0
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)
예제 #4
0
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]
예제 #5
0
파일: model.py 프로젝트: Pitrified/snippet
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'))
예제 #7
0
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'
예제 #8
0
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)