Add additional controls if JPG is selected Certain file formats accept additional options which can be specified as keyword arguments . Currently only the jpeg encoder accepts additional options which are :

def BuildPage ( self ):
		#### TODO: 	Add Rotation. Cleanup and organize controls
		#			Add handling of Image Effect Params

		#----------- Select port for image captures ------------
		f1 = MyLabelFrame(self,'Select port for image captures',0,0,span=2)
		self.UseVidPort = MyBooleanVar(False)
		self.UseRadio = MyRadio(f1,'Use Still Port',False,self.UseVidPort,
					self.UseVideoPort,0,0,'W',tip=100)
		MyRadio(f1,'Use Video Port',True,self.UseVidPort,
					self.UseVideoPort,0,1,'W',tip=101)
		f2 = ttk.Frame(f1)			# Sub frame
		f2.grid(row=1,column=0,columnspan=4,sticky='NSEW')
		self.VideoDenoise = MyBooleanVar(True)
		b = ttk.Checkbutton(f2,text='Video denoise',variable=self.VideoDenoise,
			command=self.VideoDenoiseChecked)
		b.grid(row=1,column=0,sticky='NW',padx=5)
		ToolTip(b,msg=103)
		self.VideoStab = MyBooleanVar(False)
		b = ttk.Checkbutton(f2,text='Video stabilization',variable=self.VideoStab,
			command=self.VideoStabChecked)
		b.grid(row=1,column=1,sticky='NW')
		ToolTip(b, msg=105)
		self.ImageDenoise = MyBooleanVar(True)
		b = ttk.Checkbutton(f2,text='Image denoise',variable=self.ImageDenoise,
			command=self.ImageDenoiseChecked)
		b.grid(row=1,column=2,sticky='NW',padx=10)
		ToolTip(b, msg=104)

		#--------------- Picture/Video Capture Size ---------------
		f = MyLabelFrame(self,'Picture/Video capture size in pixels',1,0)
		#f.columnconfigure(0,weight=1)
		f1 = ttk.Frame(f)			# Sub frames to frame f
		f1.grid(row=1,column=0,sticky='NSEW')
		f1.columnconfigure(1,weight=1)
		self.UseFixedResolutions = BooleanVar()
		self.UseFixedResolutions.set(True)
		self.UseFixedResRadio = ttk.Radiobutton(f1,text='Use fixed:',
			variable=self.UseFixedResolutions,
			value=True,command=self.UseFixedResRadios,padding=(5,5,5,5))
		ToolTip(self.UseFixedResRadio,120)
		self.UseFixedResRadio.grid(row=0,column=0,sticky='NW')
		self.FixedResolutionsCombo = Combobox(f1,state='readonly',width=25)
		self.FixedResolutionsCombo.bind('<<ComboboxSelected>>',
			self.FixedResolutionChanged)
		self.FixedResolutionsCombo.grid(row=0,column=1,columnspan=3,sticky='W')
		ToolTip(self.FixedResolutionsCombo,121)
		#------------ Capture Width and Height ----------------
		# OrderedDict is used to ensure the keys stay in the same order as
		# entered. I want the combobox to display in this order
		#### TODO: 	Must check resolution and framerate and disable the Video
		# 			button if we exceed limits of the modes
		# Framerate 1-30 fps up to 1920x1080		16:9 aspect ratio
		# Framerate 1-15 fps up to 2592 x 1944		 4:3 aspect ratio
		# Framerate 0.1666 to 1 fps up to 2592 x 1944 4:3 aspect ratio
		# Framerate 1-42 fps up t0 1296 x 972 		 4:3 aspect ratio
		# Framerate 1-49 fps up to 1296 x 730		16:9 aspect ratio
		# Framerate 42.1 - 60 fps to 640 x 480		 4:3 aspect ratio
		# Framerate 60.1 - 90 fps to 640 x 480		 4:3 aspect ratio
		self.StandardResolutions = OrderedDict([ \
			('CGA', (320,200)),			('QVGA', (320,240)),
			('VGA', (640,480)),			('PAL', (768,576)),
			('480p', (720,480)),		('576p', (720,576)),
			('WVGA', (800,480)),		('SVGA', (800,600)),
			('FWVGA', (854,480)),		('WSVGA', (1024,600)),
			('XGA', (1024,768)),		('HD 720', (1280,720)),
			('WXGA_1', (1280,768)),		('WXGA_2', (1280,800)),
			('SXGA', (1280,1024)),		('SXGA+', (1400,1050)),
			('UXGA', (1600,1200)),		('WSXGA+', (1680,1050)),
			('HD 1080', (1920,1080)), 	('WUXGA', (1920,1200)),
			('2K', (2048,1080)),		('QXGA', (2048, 1536)),
			('WQXGA', (2560,1600)),		('MAX Resolution', (2592,1944)),
		   ])
		vals = []
		for key in self.StandardResolutions.keys():
			vals.append('%s: (%dx%d)' % (key, # Tabs not working?!!
							self.StandardResolutions[key][0],
							self.StandardResolutions[key][1]))
		self.FixedResolutionsCombo['values'] = vals
		self.FixedResolutionsCombo.current(10)

		f2 = ttk.Frame(f)		# subframe to frame f
		f2.grid(row=2,column=0,sticky='NSEW')
		f2.columnconfigure(2,weight=1)
		f2.columnconfigure(4,weight=1)
		b2 = ttk.Radiobutton(f2,text='Roll your own:',
			variable=self.UseFixedResolutions,
			value=False,command=self.UseFixedResRadios,padding=(5,5,5,5))
		b2.grid(row=1,column=0,sticky='NW')
		ToolTip(b2,122)

		Label(f2,text="Width:",anchor=E).grid(column=1,row=1,sticky='E',ipadx=3,ipady=3)
		Widths = []
		for i in range(1,82):
			Widths.append(32 * i) # Widths can be in 32 byte increments
		self.cb = MyComboBox ( f2, Widths, current=10,
			callback=self.ResolutionChanged, width=5, row=1, col=2,
			sticky='W', state='disabled', tip=123)

		Label(f2,text="Height:",anchor=E).grid(column=3,row=1,sticky='W',ipadx=5,ipady=3)
		Heights = []
		for i in range(1,123):
			Heights.append(16 * i)	# heights in 16 byte increments
		self.cb1 = MyComboBox ( f2, Heights, current=10,
			callback=self.ResolutionChanged, width=5, row=1, col=4,
			sticky='W', state='disabled', tip=124)

		ttk.Label(f2,text='Actual:').grid(row=2,column=1,sticky='E')
		self.WidthLabel = ttk.Label(f2,style='DataLabel.TLabel')
		self.WidthLabel.grid(row=2,column=2,sticky='W')
		ToolTip(self.WidthLabel,125)
		ttk.Label(f2,text='Actual:').grid(row=2,column=3,sticky='E')
		self.HeightLabel = ttk.Label(f2,style='DataLabel.TLabel')
		self.HeightLabel.grid(row=2,column=4,sticky='W')
		ToolTip(self.HeightLabel,126)

		Separator(f,orient=HORIZONTAL).grid(pady=5,row=3,column=0,
			columnspan=4,sticky='EW')

		#--------------- Zoom Region Before ----------------
		f4 = MyLabelFrame(f,'Zoom region of interest before taking '+
			'picture/video',4,0)
		#f4.columnconfigure(1,weight=1)
		#f4.columnconfigure(3,weight=1)
		Label(f4,text='X:').grid(row=0,column=0,sticky='E')
		self.Xzoom = ttk.Scale(f4,from_=0.0,to=0.94,orient='horizontal')
		self.Xzoom.grid(row=0,column=1,sticky='W',padx=5,pady=3)
		self.Xzoom.set(0.0)
		ToolTip(self.Xzoom,130)
		Label(f4,text='Y:').grid(row=0,column=2,sticky='E')
		self.Yzoom = ttk.Scale(f4,from_=0.0,to=0.94,orient='horizontal')
		self.Yzoom.grid(row=0,column=3,sticky='W',padx=5,pady=3)
		self.Yzoom.set(0.0)
		ToolTip(self.Yzoom,131)
		Label(f4,text='Width:').grid(row=1,column=0,sticky='E')
		self.Widthzoom = ttk.Scale(f4,from_=0.05,to=1.0,orient='horizontal')
		self.Widthzoom.grid(row=1,column=1,sticky='W',padx=5,pady=3)
		self.Widthzoom.set(1.0)
		ToolTip(self.Widthzoom,132)
		Label(f4,text='Height:').grid(row=1,column=2,sticky='E')
		self.Heightzoom = ttk.Scale(f4,from_=0.05,to=1.0,orient='horizontal')
		self.Heightzoom.grid(row=1,column=3,sticky='W',padx=5,pady=3)
		self.Heightzoom.set(1.0)
		ToolTip(self.Heightzoom,133)
		# WLW THIS IS A PROBLEM
		image = PIL.Image.open('Assets/reset.png') #.resize((16,16))
		self.resetImage = GetPhotoImage(image.resize((16,16)))
		self.ResetZoomButton = ttk.Button(f4,image=self.resetImage,
			command=self.ZoomReset)
		self.ResetZoomButton.grid(row=0,column=4,rowspan=2,sticky='W')
		ToolTip(self.ResetZoomButton,134)

		self.Xzoom.config(command=lambda newval,
			widget=self.Xzoom:self.Zoom(newval,widget))
		self.Yzoom.config(command=lambda newval,
			widget=self.Yzoom:self.Zoom(newval,widget))
		self.Widthzoom.config(command=lambda newval,
			widget=self.Widthzoom:self.Zoom(newval,widget))
		self.Heightzoom.config(command=lambda newval,
			widget=self.Heightzoom:self.Zoom(newval,widget))

		Separator(f,orient=HORIZONTAL).grid(pady=5,row=5,column=0,
			columnspan=3,sticky='EW')

		#--------------- Resize Image After ----------------
		f4 = MyLabelFrame(f,'Resize image after taking picture/video',6,0)
		#f4.columnconfigure(3,weight=1)
		#f4.columnconfigure(5,weight=1)

		b = MyBooleanVar(False)
		self.ResizeAfterNone = MyRadio(f4,'None (Default)',False,b,
			self.AllowImageResizeAfter,0,0,'W',pad=(0,5,0,5), tip=140)
		MyRadio(f4,'Resize',True,b,self.AllowImageResizeAfter,
					0,1,'W',pad=(5,5,0,5),tip=141)

		Label(f4,text="Width:",anchor=E).grid(column=2,row=0,sticky='E',ipadx=3,ipady=3)
		self.resizeWidthAfterCombo = MyComboBox ( f4, Widths, current=10,
			callback=self.ResizeAfterChanged, width=5, row=0, col=3,
			sticky='W', state='disabled', tip=142)

		Label(f4,text="Height:",anchor=E).grid(column=4,row=0,sticky='W',ipadx=5,ipady=3)
		self.resizeHeightAfterCombo = MyComboBox ( f4, Heights, current=10,
			callback=self.ResizeAfterChanged, width=5, row=0, col=5,
			sticky='W', state='disabled', tip=143)

		self.resizeAfter = None

		#--------------- Quick Adjustments ----------------
		f = MyLabelFrame(self,'Quick adjustments',2,0)
		#f.columnconfigure(2,weight=1)
		#-Brightness
		self.brightLabel, self.brightness, val = \
			self.SetupLabelCombo(f,'Brightness:',0,0,0, 100,
				self.CameraBrightnessChanged, self.camera.brightness )
		self.CameraBrightnessChanged(val)
		ToolTip(self.brightness,msg=150)
		#-Contrast
		self.contrastLabel, self.contrast, val = \
			self.SetupLabelCombo(f,'Contrast:',0,3,-100, 100,
				self.ContrastChanged, self.camera.contrast )
		self.ContrastChanged(val)
		ToolTip(self.contrast,msg=151)
		#-Saturation
		self.saturationLabel, self.saturation, val = \
			self.SetupLabelCombo(f,'Saturation:',1,0,-100, 100,
				self.SaturationChanged, self.camera.saturation, label='Sat' )
		self.SaturationChanged(val)
		ToolTip(self.saturation,msg=152)
		#-Sharpness
		self.sharpnessLabel, self.sharpness, val = \
			self.SetupLabelCombo(f,'Sharpness:',1,3,-100, 100,
				self.SharpnessChanged, self.camera.sharpness )
		self.SharpnessChanged(val)
		ToolTip(self.sharpness,msg=153)
		#-Reset
		#self.ResetGeneralButton = Button(f,image=self.resetImage,width=5,
			#command=self.ResetGeneralSliders)
		#self.ResetGeneralButton.grid(row=4,column=2,sticky='W',padx=5)
		#ToolTip(self.ResetGeneralButton,msg=154)

		#--------------- Image Effects ----------------
		f = MyLabelFrame(self,'Preprogrammed image effects',3,0)
		#f.columnconfigure(2,weight=1)

		v = MyBooleanVar(False)
		self.NoEffectsRadio = MyRadio(f,'None (Default)',False,v,
			self.EffectsChecked,0,0,'W',tip=160)
		MyRadio(f,'Select effect:',True,v,self.EffectsChecked,0,1,'W',
			tip=161)

		self.effects = Combobox(f,height=15,width=10,state='readonly')#,width=15)
		self.effects.grid(row=0,column=2,sticky='W')
		effects = list(self.camera.IMAGE_EFFECTS.keys())	# python 3 workaround
		effects.remove('none')
		effects.sort() #cmp=lambda x,y: cmp(x.lower(),y.lower())) # not python 3
		self.effects['values'] = effects
		self.effects.current(0)
		self.effects.bind('<<ComboboxSelected>>',self.EffectsChanged)
		ToolTip(self.effects, msg=162)

		self.ModParams = ttk.Button(f,text='Params...',
			command=self.ModifyEffectsParamsPressed,underline=0,padding=(5,3,5,3),width=8)
		self.ModParams.grid(row=0,column=3,sticky=EW,padx=5)
		ToolTip(self.ModParams, msg=163)
		self.EffectsChecked(False)
		'''
				Add additional controls if JPG is selected
				Certain file formats accept additional options which can be specified as keyword
				arguments. Currently, only the 'jpeg' encoder accepts additional options, which are:

				quality - Defines the quality of the JPEG encoder as an integer ranging from 1 to 100.
							 Defaults to 85. Please note that JPEG quality is not a percentage and
							 definitions of quality vary widely.
				restart - Defines the restart interval for the JPEG encoder as a number of JPEG MCUs.
							 The actual restart interval used will be a multiple of the number of MCUs per row in the resulting image.
				thumbnail - Defines the size and quality of the thumbnail to embed in the Exif
								metadata. Specifying None disables thumbnail generation. Otherwise,
								specify a tuple of (width, height, quality). Defaults to (64, 48, 35).
				bayer - If True, the raw bayer data from the camera’s sensor is included in the
							Exif metadata.
		'''
		#--------------- Flash Mode ---------------
		f = MyLabelFrame(self,'LED and Flash mode',4,0,span=4)
		#f.columnconfigure(3,weight=1)
		self.LedOn = MyBooleanVar(True)
		self.LedButton = ttk.Checkbutton(f,text='Led On (via GPIO pins)',
			variable=self.LedOn, command=self.LedOnChecked)
		self.LedButton.grid(row=0,column=0,sticky='NW',pady=5, columnspan=2)
		ToolTip(self.LedButton,msg=102)
		Label(f,text='Flash Mode:').grid(row=1,column=0,sticky='W')
		b = MyStringVar('off')
		self.FlashModeOffRadio = MyRadio(f,'Off (Default)','off',b,
			self.FlashModeButton,1,1,'W',tip=180)
		MyRadio(f,'Auto','auto',b,self.FlashModeButton,1,2,'W',tip=181)
		MyRadio(f,'Select:','set',b,self.FlashModeButton,1,3,'W',tip=182)
		# Use invoke() on radio button to force a command
		self.FlashModeCombo = Combobox(f,state='readonly',width=10)
		self.FlashModeCombo.grid(row=1,column=4,sticky='W')
		self.FlashModeCombo.bind('<<ComboboxSelected>>',self.FlashModeChanged)
		modes = list(self.camera.FLASH_MODES.keys())
		modes.remove('off')		# these two are handled by radio buttons
		modes.remove('auto')
		modes.sort() #cmp=lambda x,y: cmp(x.lower(),y.lower()))
		self.FlashModeCombo['values'] = modes
		self.FlashModeCombo.current(0)
		self.FlashModeCombo.config(state='disabled')
		ToolTip(self.FlashModeCombo,183)

		self.FixedResolutionChanged(None)


Change how the edit fields work . Allow a / in the edit field to denote a fraction . Split on / and evaluate each side . Note if <num > / then num is evalulated since den is empty

def ValidateEntry ( self, entry, minVal, maxVal ):
		'''
		Change how the edit fields work. Allow a '/' in the edit field to
		denote a fraction.
		Split on '/' and evaluate each side. Note, if '<num> /' then
		num is evalulated since 'den' is empty
		'''
		vals = entry.split('/',1)	# entry is text
		val = vals[0].strip()
		try:		num = float(val)
		except:	num = None
		else:
			if len(vals) > 1:
				val = vals[1].strip()
				if val:
					try:			den = float(val)
					except:		num = None
					else:
						if den > 0:	num = num / den
						else:			num = None
		if num is not None:
			num = num if num >= minVal and num <= maxVal else None
		self.FrameRate.config(style='RedMessage.TLabel' if num is None \
									  else 'DataLabel.TLabel')
		return num

def
ValidateEntry
(
self
,
entry
,
minVal
,
maxVal
)
:
vals
=
entry
.
split
(
'/'
,
1
)
# entry is text
val
=
vals
[
0
]
.
strip
(
)
try
:
num
=
float
(
val
)
except
:
num
=
else
:
if
len
(
vals
)
>
1
:
val
=
vals
[
1
]
.
strip
(
)
if
val
:
try
:
den
=
float
(
val
)
except
:
num
=
else
:
if
den
>
0
:
num
=
num
/
den
else
:
num
=
if
num
is
not
:
num
=
num
if
num
>=
minVal
and
num
<=
maxVal
else
self
.
FrameRate
.
config
(
style
=
'RedMessage.TLabel'
if
num
is
else
'DataLabel.TLabel'
)
return
num


Initialize the ToolTip Arguments : wdgt : The widget to which this ToolTip is assigned msg : A static string message assigned to the ToolTip if msg istype integer - search for text in TipLines msgFunc : A function that retrieves a string to use as the ToolTip text delay : The delay in seconds before the ToolTip appears ( may be float ) follow : If True the ToolTip follows motion otherwise hides

def __init__( self, wdgt, msg=None, msgFunc=None, follow=1 ):
		"""
		Initialize the ToolTip
		Arguments:
			wdgt: The widget to which this ToolTip is assigned
			msg:  A static string message assigned to the ToolTip
					if msg istype integer - search for text in TipLines
			msgFunc: A function that retrieves a string to use as the ToolTip text
			delay:   The delay in seconds before the ToolTip appears(may be float)
			follow:  If True, the ToolTip follows motion, otherwise hides
		"""
		self.wdgt = wdgt
		# The parent of the ToolTip is the parent of the ToolTips widget
		self.parent = self.wdgt.master
		# Initalise the Toplevel
		Toplevel.__init__( self, self.parent, bg='black', padx=1, pady=1 )
		self.withdraw()  # Hide initially
		# The ToolTip Toplevel should have no frame or title bar
		self.overrideredirect( True )
		# The msgVar will contain the text displayed by the ToolTip
		self.msgVar = StringVar()
		self.TipID = None
		self.TipNumText = ""
		try:
			if msg is None:
				self.msgVar.set('No tooltip provided')
			elif type(msg) is int:	# lookup tooltip text in file
				self.TipID = msg
				self.msgVar.set(ToolTip.GetTooltipText(msg))
				self.TipNumText = "Tip number %d\n\n" % self.TipID
			else:	# assume a string is passed
				self.msgVar.set( msg )
		except:
			self.msgVar.set('ERROR getting tooltip')
		self.msgFunc = msgFunc		# call this function to return tip text
		self.follow = follow			# move tip if mouse moves
		self.visible = 0
		self.lastMotion = 0
		# The test of the ToolTip is displayed in a Message widget
		Message( self, textvariable=self.msgVar, bg='#FFFFDD',
					aspect=250 ).grid()
		# Add bindings to the widget.  This will NOT override bindings
		# that the widget already has
		self.wdgt.bind( '<Enter>', self.spawn, '+' )
		self.wdgt.bind( '<Leave>', self.hide, '+' )
		self.wdgt.bind( '<Motion>', self.move, '+' )


Spawn the ToolTip . This simply makes the ToolTip eligible for display . Usually this is caused by entering the widget Arguments : event : The event that called this funciton

def spawn( self, event=None ):
		"""
		Spawn the ToolTip.  This simply makes the ToolTip eligible for display.
		Usually this is caused by entering the widget
		Arguments:
			event: The event that called this funciton
		"""
		self.visible = 1
		# The after function takes a time argument in miliseconds
		self.after( int( ToolTip.ShowTipDelay * 1000 ), self.show )


def show( self ):
		"""
		Displays the ToolTip if the time delay has been long enough
		"""
		if ToolTip.ShowToolTips is False: return
		text = self.msgVar.get()
		if ToolTip.ShowTipNumber is True and self.TipID is not None:
			# check if text is not there, if so add it
			if self.TipNumText not in text:
				self.msgVar.set(self.TipNumText+text)
		else:
			text.replace(self.TipNumText,"")
			self.msgVar.set(text)

		if self.visible == 1 and time() - self.lastMotion > ToolTip.ShowTipDelay:
			self.visible = 2
		if self.visible == 2:
			self.deiconify()


Processes motion within the widget . Arguments : event : The event that called this function

def move( self, event ):
		"""
		Processes motion within the widget.
		Arguments:
		event: The event that called this function
		"""
		self.lastMotion = time()
		# If the follow flag is not set, motion within the widget will
		# make the ToolTip dissapear
		if self.follow == False:
			self.withdraw()
			self.visible = 1
		# Offset the ToolTip 10x10 pixels southeast of the pointer
		self.geometry( '+%i+%i' % ( event.x_root+10, event.y_root+10 ) )
		# Try to call the message function.  Will not change the message
		# if the message function is None or the message function fails
		try:		self.msgVar.set( self.msgFunc() )
		except:	pass
		self.after( int( ToolTip.ShowTipDelay * 1000 ), self.show )


Hides the ToolTip . Usually this is caused by leaving the widget Arguments : event : The event that called this function

def hide( self, event=None ):
		"""
		Hides the ToolTip.  Usually this is caused by leaving the widget
		Arguments:
			event: The event that called this function
		"""
		self.visible = 0
		self.withdraw()

def hide ( self , event = None ) : self . visible = 0 self . withdraw ( )


Can t seem to get a foucs highlight around some of the controls . So I m using color changes to show focus / tab stops . Also the Scale does not seem to get focus by clicking on it Need to force focus when the user clicks on it

def SetControlMapping ( self ):
		#Style().configure('.', font=('Helvetica', 12)) # all
		Style().configure('RedMessage.TLabel',font=('Arial',10,"italic"),foreground='red')
		# These dont work since they're overriden by the map later on... ???
		Style().configure('Error.TEntry',background='red',foreground='white')
		Style().configure('OK.TEntry',background='white',foreground='black')
		Style().configure('DataLabel.TLabel',foreground='blue',font=('Arial',10))
		Style().configure('StatusBar.TLabel',background=self.FocusColor,relief=SUNKEN)
		Style().configure('TMenu',background='white',activeforeground='lightblue')
		'''
		Can't seem to get a foucs highlight around some of the controls.
		So I'm using color changes to show focus/tab stops.
		Also, the Scale does not seem to get focus by clicking on it
		Need to force focus when the user clicks on it
		'''
		Style().map('TPanedwindow',
			background = [
								('!active','#f0f0ff'),
							 ],
			)
		Style().map('TCombobox',
			fieldbackground = [
							  ('focus','!disabled',self.FocusColor),
							  ('!focus','active','!disabled',self.NoFocusMouseOverColor),
							  ('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
							 #('disabled','lightgray'), # Use foreground
							  ],
			foreground = [
						 ('disabled','gray'),
						 ('!disabled', 'black')
						 ],
			selectbackground = [
							   ('focus','!disabled',self.FocusColor),
							   ('!focus','active','!disabled',self.NoFocusMouseOverColor),
							   ('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
							  #('disabled','lightgray'),
							   ],
			selectforeground = [
							   ('!focus','black'),
							   ('readonly','black'),
							   ('focus','black'),
							   ('disabled','black'),
							   ('!disabled', 'black')
							   ],
				   )	# close map

		Style().map('TEntry',# This one is just for 'look and feel'
			fieldbackground = [
							  ('focus','!disabled', '!invalid' ,self.FocusColor),
							  ('!focus','active','!disabled','!invalid', self.NoFocusMouseOverColor),
							  ('!focus','!active','!disabled','!invalid',self.NoFocusNoMouseOverColor),
							  ('invalid', '#FF0000')
							 #('disabled','lightgray'),
							  ],
			foreground = [
						 ('disabled', '!invalid', 'gray'),
						 ('!disabled', '!invalid', 'black')
						 #('invalid', self.NoFocusNoMouseOverColor)
						 ],
			#background = [
						  #('focus','!disabled',self.FocusColor),
						  #('!focus','active','!disabled',self.NoFocusMouseOverColor'),
						  #('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
						 ##('disabled','lightgray'),
						  #],
			#selectbackground = [
							   #('focus','!disabled',self.FocusColor),
							   #('!focus','active','!disabled',self.NoFocusMouseOverColor),
							   #('!focus','!active','!disabled',self.NoFocusNoMouseOverColor).
							  ##('disabled','lightgray'),
							   #],
			selectforeground = [
							   ('!focus','black'),
							   ('focus','white'),
							   ],
				   ) # close map

		#Style().map('TMenubutton',# This one is just for 'look and feel'
			#fieldbackground = [
							  #('focus','!disabled',self.FocusColor),
							  #('!focus','active','!disabled',self.NoFocusMouseOverColor),
							  #('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
							 ##('disabled','lightgray'),
							  #],
			#foreground = [
						 #('disabled','gray'),
						 #('!disabled', 'black')
						 #],
			#background = [
						  #('focus','!disabled',self.FocusColor),
						  #('!focus','active','!disabled',self.NoFocusMouseOverColor),
						  #('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
						 ##('disabled','lightgray'),
						  #],
			#selectbackground = [
							   #('focus','!disabled',self.FocusColor),
							   #('!focus','active','!disabled',self.NoFocusMouseOverColor),
							   #('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
							  ##('disabled','lightgray'),
							   #],
			#selectforeground = [
							   #('!focus','black'),
							   #('focus','white'),
							   #],
				   #) # close map

		Style().map("Horizontal.TScale",
			troughcolor = [
						  ('focus','!disabled',self.FocusColor),
						  ('!focus','active','!disabled',self.NoFocusMouseOverColor),
						  ('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
						  ('disabled','lightgray'),
						  ],
				   ) # close map

		Style().map("Vertical.TScale",
			troughcolor = [
						  ('focus','!disabled',self.FocusColor),
						  ('!focus','active','!disabled',self.NoFocusMouseOverColor),
						  ('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
						  ('disabled','lightgray'),
						  ],
				   ) # close map

		Style().map("TMenu",
			background = [
						  ('focus','!disabled',self.FocusColor),
						  ('!focus','active','!disabled',self.NoFocusMouseOverColor),
						  ('!focus','!active','!disabled',self.NoFocusNoMouseOverColor),
						  ('disabled','lightgray'),
						  ],
				   )


def LoadImageFromStream ( self, zoom ):
		if self.photo: del self.photo

		self.pictureStream.seek(0)
		self.CurrentImage = PIL.Image.open(self.pictureStream)
		# https://pillow.readthedocs.io/en/3.1.x/reference/Image.html#the-image-class
		self.RawEXIFData = None
		if PreferencesDialog.DefaultPhotoFormat == 'jpeg':
			self.RawEXIFData = self.CurrentImage.info['exif']	# Works!!!!
			#print ( self.RawEXIFData )
		self.CameraUtils.AddEXIFTags(self.CurrentImage)
		self.ShowHideImageAttributesPane(self.viewImageAttributesPane.get())

		# resize what's displayed if user used Ctrl+mousewheel
		size = self.CurrentImage.size
		if size[0] <= 1024 and size[1] <= 768:	# hold max zoom level
			width = int(zoom*size[0])
			height = int(zoom*size[1])
			if width <= 1024 and height <= 768:
				self.CurrentImage = self.CurrentImage.resize((width,height),PIL.Image.ANTIALIAS)
		self.CurrentImageSize = self.CurrentImage.size

		# Convert to canvas compatible format and store on canvas
		self.photo = ImageTk.PhotoImage(self.CurrentImage)
		self.photoCanvas.delete("pic")
		self.photoCanvas.create_image(0,0,image=self.photo,anchor='nw',tags=('pic'))
		self.photoCanvas.config(scrollregion=self.photoCanvas.bbox(ALL))
		self.photoCanvas.tag_raise("objs") # raise Z order of cursors to topmost
		'''
			Implement Record Sequence
		'''


The Combobox is a problem . Could save last two entries ... if Combobox Labelframe Tk ... NO! this could be the Topwindow losing focus while the Combobox has the focus . The same effect Also the nesting may vary based on where the Combobox is in the hierarcy . What I really want is to capture the <B1 - Motion > on the TopWindow titlebar - OR - get the widget ID to the Combobox Toplevel dropdown window .

def LoseFocus ( self, event ):
		'''
							The Combobox is a problem.
		Could save last two entries... if Combobox, Labelframe, Tk...
		NO! this could be the Topwindow losing focus while the
		Combobox has the focus.  The same effect
		Also, the nesting may vary based on where the Combobox is in
		the hierarcy. What I really want is to capture the <B1-Motion>
		on the TopWindow titlebar - OR - get the widget ID to the
		Combobox Toplevel dropdown window.
		'''
		if self.camera.preview and not self.AlwaysPreview and \
			event.widget.winfo_class().lower() == 'tk' and \
			self.root.attributes("-topmost") == 0: # TopMost window hack
			if self.ShowOnScreen.get() == False:
				self.camera.preview.alpha = 0
			self.ImageCanvas.itemconfigure('nopreview',state='normal')

def
LoseFocus
(
self
,
event
)
:
if
self
.
camera
.
preview
and
not
self
.
AlwaysPreview
and
event
.
widget
.
winfo_class
(
)
.
lower
(
)
==
'tk'
and
self
.
root
.
attributes
(
"-topmost"
)
==
0
:
# TopMost window hack
if
self
.
ShowOnScreen
.
get
(
)
==
False
:
self
.
camera
.
preview
.
alpha
=
0
self
.
ImageCanvas
.
itemconfigure
(
'nopreview'
,
state
=
'normal'
)


Configuration files in python There are several ways to do this depending on the file format required . ConfigParser [ . ini format ] Write a file like so : from ConfigParser import SafeConfigParser config = SafeConfigParser () config . read ( config . ini ) config . add_section ( main ) config . set ( main key1 value1 ) config . set ( main key2 value2 ) config . set ( main key3 value3 )

def BuildPage ( self ):
		# Setup default folder to save pictures and videos
		f = MyLabelFrame(self,'Set default directories',0,0)

		self.iconCameraBig = PIL.Image.open('Assets/camera-icon.png')
		self.iconCameraBig = ImageTk.PhotoImage(self.iconCameraBig.resize((22,22),Image.ANTIALIAS))
		self.iconVideoBig = PIL.Image.open('Assets/video-icon-b.png')
		self.iconVideoBig = ImageTk.PhotoImage(self.iconVideoBig.resize((22,22),Image.ANTIALIAS))
		self.iconFiles = PIL.Image.open('Assets/files.png')
		self.iconFiles = ImageTk.PhotoImage(self.iconFiles.resize((22,22),Image.ANTIALIAS))

		b = ttk.Button(f,text="Photos...",image=self.iconCameraBig,compound='left',
			command=self.SelectPhotoDirectory,width=7)
		b.grid(row=0,column=0,sticky='W',pady=(5,5))
		ToolTip(b,6000)
		self.PhotoDirLabel = Label(f,foreground='#0000FF',
			text=PreferencesDialog.DefaultPhotoDir,anchor=W)
		self.PhotoDirLabel.grid(row=0,column=1,sticky='EW',padx=10);
		ToolTip(self.PhotoDirLabel,6001)

		b = ttk.Button(f,text="Videos...",image=self.iconVideoBig,compound='left',
			command=self.SelectVideoDirectory,width=7)
		b.grid(row=1,column=0,sticky='W')
		ToolTip(b,6002)
		self.VideoDirLabel = Label(f,foreground='#0000FF',
			text=PreferencesDialog.DefaultVideoDir,anchor=W)
		self.VideoDirLabel.grid(row=1,column=1,sticky='EW',padx=10);
		ToolTip(self.VideoDirLabel,6003)

		b = ttk.Button(f,text="Files...",image=self.iconFiles,compound='left',
			command=self.SelectFilesDirectory,width=7)
		b.grid(row=2,column=0,sticky='W',pady=(5,5))
		ToolTip(b,6004)
		self.FilesDirLabel = Label(f,foreground='#0000FF',
			text=PreferencesDialog.DefaultFilesDir,anchor=W)
		self.FilesDirLabel.grid(row=2,column=1,sticky='EW',padx=10);
		ToolTip(self.FilesDirLabel,6005)

		f = MyLabelFrame(self,'Photo/Video capture formats',1,0)

		ttk.Label(f,text='Photo capture format',padding=(5,5,5,5)) \
			.grid(row=0,column=0,sticky='W')
		self.photoCaptureFormatCombo = Combobox(f,height=15,width=8,
			state='readonly')#,width=15)
		self.photoCaptureFormatCombo.grid(row=0,column=1,sticky='EW')
		self.photoFormats = ['jpeg','png','bmp',
			'gif','yuv','rgb','rgba','bgr','bgra','raw']
		self.photoCaptureFormatCombo['values'] = self.photoFormats
		self.photoCaptureFormatCombo.current( \
			self.photoFormats.index(PreferencesDialog.DefaultPhotoFormat))
		self.photoCaptureFormatCombo.bind('<<ComboboxSelected>>',
			self.photoCaptureFormatChanged)
		ToolTip(self.photoCaptureFormatCombo, msg=6010)
		self.ModFormatParams = ttk.Button(f,text='Params...',
			command=self.ModifyFormatParamPressed,
			underline=0,padding=(5,3,5,3),width=8)
		self.ModFormatParams.grid(row=0,column=2,sticky='W',padx=5)
		ToolTip(self.ModFormatParams, msg=6011)

		ttk.Label(f,text='Video capture format',padding=(5,5,5,5)) \
			.grid(row=1,column=0,sticky='W')
		self.VideoCaptureFormatCombo = Combobox(f,height=15,width=8,
			state='readonly')#,width=15)
		self.VideoCaptureFormatCombo.grid(row=1,column=1,sticky='EW')
		self.videoFormats = ['h264','mjpeg','yuv',
			'rgb','rgba','bgr','bgra']
		self.VideoCaptureFormatCombo['values'] = self.videoFormats
		self.VideoCaptureFormatCombo.current( \
			self.videoFormats.index(PreferencesDialog.DefaultVideoFormat))
		self.VideoCaptureFormatCombo.bind('<<ComboboxSelected>>',
			self.VideoCaptureFormatChanged)
		ToolTip(self.VideoCaptureFormatCombo,6020)
		self.ModVideoFormatParams = ttk.Button(f,text='Params...',
			command=self.ModifyVideoFormatParamPressed,
			underline=0,padding=(5,3,5,3),width=8)
		self.ModVideoFormatParams.grid(row=1,column=2,sticky='W',padx=5)
		ToolTip(self.ModVideoFormatParams,6021)
		# Save / Restore camera settings? This may be a bit to do

		f = MyLabelFrame(self,'Photo/Video naming',2,0)
		Label(f,text='Timestamp format:') \
			.grid(row=0,column=0,sticky='W')
		okCmd = (self.register(self.ValidateTimestamp),'%P')
		self.TimeStamp = MyStringVar(PreferencesDialog.DefaultTimestampFormat)
		e = Entry(f,width=20,validate='all',
			textvariable=self.TimeStamp)
		e.grid(row=0,column=1,sticky='W')
		ToolTip(e,6050)

		image = PIL.Image.open('Assets/help.png')
		self.helpimage = ImageTk.PhotoImage(image.resize((16,16)))
		b = ttk.Button(f,image=self.helpimage,width=10,
			command=self.FormatHelp,padding=(2,2,2,2))
		b.grid(row=0,column=2,padx=5)
		ToolTip(b,6052)

		Label(f,text='Sample timestamp:').grid(row=1,column=0,sticky='W')
		self.TimestampLabel = MyStringVar(datetime.datetime.now() \
			.strftime(PreferencesDialog.DefaultTimestampFormat))
		self.tsl = Label(f,textvariable=self.TimestampLabel,foreground='#0000FF')
		self.tsl.grid(row=1,column=1,columnspan=2,sticky='W')
		ToolTip(self.tsl,6051)
		self.after(1000,self.UpdateTimestamp)

		self.PhotoTimestampVar = MyBooleanVar(PreferencesDialog.PhotoTimestamp)
		self.PhotoTimestamp = Checkbutton(f,text='Include timestamp in photo name',
			variable=self.PhotoTimestampVar, command=self.PhotoTimestampChecked)
		self.PhotoTimestamp.grid(row=2,column=0,columnspan=2,sticky='W')
		ToolTip(self.PhotoTimestamp,6060)

		self.VideoTimestampVar = MyBooleanVar(PreferencesDialog.VideoTimestamp)
		self.VideoTimestamp = Checkbutton(f,text='Include timestamp in video name',
			variable=self.VideoTimestampVar, command=self.VideoTimestampChecked)
		self.VideoTimestamp.grid(row=3,column=0,columnspan=2,sticky='W')
		ToolTip(self.VideoTimestamp,6061)

		e.config(validatecommand=okCmd)
		'''
		Configuration files in python
		There are several ways to do this depending on the file format required.
		ConfigParser [.ini format]
		Write a file like so:
			from ConfigParser import SafeConfigParser
			config = SafeConfigParser()
			config.read('config.ini')
			config.add_section('main')
			config.set('main', 'key1', 'value1')
			config.set('main', 'key2', 'value2')
			config.set('main', 'key3', 'value3')
		'''
		self.photoCaptureFormatChanged(None)
		self.VideoCaptureFormatChanged(None)