def __init__(self, tkparent, title, objtype, field_content, show_refs):
        self.mon = Monitor()

        if objtype == SHOW:
            self.record_specs = PPdefinitions.show_types
            self.field_specs = PPdefinitions.show_field_specs
            self.initial_tab = "show"
        elif objtype == TRACK:
            self.record_specs = PPdefinitions.track_types
            self.field_specs = PPdefinitions.track_field_specs
            self.initial_tab = "track"

        # save the extra arg to instance variable
        self.objtype = objtype
        self.field_content = field_content  # dictionary - the track parameters to be edited
        self.show_refs = show_refs
        self.show_refs.append("")
        self.initial_media_dir = pp_paths.media_dir
        self.pp_home_dir = pp_paths.pp_home

        # list of stringvars from which to get edited values (for optionmenu only??)
        self.entries = []

        # lookup for widget when all we have is the field
        self.widgets = {}

        self.validator = Validator()
        self.validator.initialize(scope=objtype)
        if objtype == SHOW:
            self.mon.log(self, "Editing show '{0} -------------------".format(Validator.get_showref))
            self.validator.set_current(show=field_content)
        elif objtype == TRACK:
            self.mon.log(self, "Editing track '{0} -------------------".format(Validator.get_trackref))
            self.validator.set_current(track=field_content)
        self.tempcontent = copy.deepcopy(self.field_content)

        # and call the base class _init_which calls body immeadiately and apply on OK pressed
        ttkSimpleDialog.Dialog.__init__(self, tkparent, title)
Exemple #2
0
 def validate_profile(self):
     val =Validator()
     val.validate_profile(self.root,self.pp_dir,self.pp_home_dir,self.pp_profile_dir,self.editor_issue,True)
    def __init__(self):
        
        self.pipresents_issue="1.2"
        self.nonfull_window_width = 0.6 # proportion of width
        self.nonfull_window_height= 0.6 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner
        
        StopWatch.global_enable=False

#****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get pi presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit()

        
        #Initialise logging
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        self.mon.on()
        if self.options['debug']==True:
            Monitor.global_enable=True
        else:
            Monitor.global_enable=False
 
        self.mon.log (self, "Pi Presents is starting")
        self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME'))
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))

        # optional other classes used
        self.ppio=None
        self.tod=None
         
        #get profile path from -p option
        if self.options['profile']<>"":
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"
        
       #get directory containing pp_home from the command,
        if self.options['home'] =="":
            home = os.path.expanduser('~')+ os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)
        
        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home=pp_dir+"/pp_home"
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found==True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:    
            self.mon.log(self,"FAILED to find requested home directory, using default to display error message: " + self.pp_home)


        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile"   
            self.mon.log(self,"FAILED to find requested profile, using default to display error message: pp_profile")
        
        if self.options['verify']==True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False:
                tkMessageBox.showwarning("Pi Presents","Validation Failed")
                exit()
                
        # open the resources
        self.rr=ResourceReader()
        # read the file, done once for all the other classes to use.
        if self.rr.read(pp_dir,self.pp_home,self.pp_profile)==False:
            self.end('error','cannot find resources.cfg')            

        #initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error','showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue())<>float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self.end('error','wrong version of profile')
 
        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error','start show not found')

        
# ********************
# SET UP THE GUI
# ********************
        #turn off the screenblanking and saver
        if self.options['noblank']==True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')
        
        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions and decorations
        if self.options['fullscreen']==True:

            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')
        else:
            self.window_width=int(self.screen_width*self.nonfull_window_width)
            self.window_height=int(self.screen_height*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))

            
        #canvas covers the whole window
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
        
        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.exit_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height,
                                       width=self.canvas_width,
                                       highlightthickness=0)
        # self.canvas.pack()
        self.canvas.place(x=0,y=0)

        self.canvas.focus_set()

                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # looks after bindings between symbolic names and internal operations
        controlsmanager=ControlsManager()
        if controlsmanager.read(pp_dir,self.pp_home,self.pp_profile)==False:
                self.end('error','cannot find or error in controls.cfg.cfg')
        else:
            controlsmanager.parse_defaults()

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile)==False:
                self.end('error','cannot find or error in keys.cfg')
        kbd.bind_keys(self.root,self.input_pressed)

        self.sr=ScreenDriver()
        # read the screen click area config file
        if self.sr.read(pp_dir,self.pp_home,self.pp_profile)==False:
            self.end('error','cannot find screen.cfg')

        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        reason,message = self.sr.make_click_areas(self.canvas,self.input_pressed)
        if reason=='error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        
        #kick off GPIO if enabled by command line option
        if self.options['gpio']==True:
            from pp_gpio import PPIO
            # initialise the GPIO
            self.ppio=PPIO()
            # PPIO.gpio_enabled=False
            if self.ppio.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.gpio_pressed)==False:
                self.end('error','gpio error')
                
            # and start polling gpio
            self.ppio.poll()

        #kick off the time of day scheduler
        self.tod=TimeOfDay()
        self.tod.init(pp_dir,self.pp_home,self.canvas,500)
        self.tod.poll()


        # Create list of start shows initialise them and then run them
        self.run_start_shows()

        #start tkinter
        self.root.mainloop( )
    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3.2"
        self.pipresents_minorissue = '1.3.2a'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner


        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit(102)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'GapShow','Show','ArtShow',
                            'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player',
                            'MediaList','LiveList','ShowList',
                            'PathManager','ControlsManager','ShowManager','PluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver',
                            'Network','Mailer'
                            ]
        

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as file:
            self.mon.log(self,'\nRaspbian: '+file.read())

        self.mon.log(self,'\n'+check_output(["omxplayer", "-v"]))
        self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"]))
        
        if "DESKTOP_SESSION" not in os.environ:
            print 'Pi Presents must be run from the Desktop'
            self.mon.log(self,'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION'])
        
        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.gpiodriver=None
        self.oscdriver=None
        self.osc_enabled=False
        self.gpio_enabled=False
        self.tod_enabled=False
        self.email_enabled=False


        if os.geteuid() == 0:
            self.mon.err(self,'Do not run Pi Presents with sudo')
            self.end('error','Do not run Pi Presents with sudo')

        
        user=os.getenv('USER')

        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work



        # check network is available
        self.network_connected=False
        self.network_details=False
        self.interface=''
        self.ip=''
        self.unit=''
        
        # sets self.network_connected and self.network_details
        self.init_network()

        
        # start the mailer and send email when PP starts
        self.email_enabled=False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M")
                message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + '   Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip 
                self.send_email('start',subject,message) 

         
        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.mon.err(self,"Profile not specified in command ")
            self.end('error','Profile not specified with the commands -p option')
        
       # get directory containing pp_home from the command,
        if self.options['home']  == "":
            home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)


        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found is True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self,"Failed to find pp_home directory at " + home)
            self.end('error',"Failed to find pp_home directory at " + home)


        # check profile exists
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self,"Running profile: " + self.pp_profile_path)
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error',"Failed to find requested profile: "+ self.pp_profile)

        self.mon.start_stats(self.options['profile'])
        
        if self.options['verify'] is True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is  False:
                self.mon.err(self,"Validation Failed")
                self.end('error','Validation Failed')

         
        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error',"showlist not found at "+self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")
            self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")


        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error',"Show [start] not found in showlist")


# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.starter_show['background-colour'])

        self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvas cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.starter_show['background-colour'])


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False:
            self.end('error','cannot find, or error in keys.cfg')
        kbd.bind_keys(self.root,self.handle_input_event)

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find, or error in screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.terminate_required=False
        self.exitpipresents_required=False

        # delete omxplayer dbus files
        # if os.path.exists("/tmp/omxplayerdbus.{}".format(user)):
            # os.remove("/tmp/omxplayerdbus.{}".format(user))
        # if os.path.exists("/tmp/omxplayerdbus.{}.pid".format(user)):
            # os.remove("/tmp/omxplayerdbus.{}.pid".format(user))
        
        # kick off GPIO if enabled by command line option
        self.gpio_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver=GPIODriver()
            reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.gpio_enabled=True
                # and start polling gpio
                self.gpiodriver.poll()
            
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
                self.oscdriver=OSCDriver()
                reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event)
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end('error',message)
                else:
                    self.osc_enabled=True
                    self.root.after(1000,self.oscdriver.start_server())

        
        # enable ToD scheduler if schedule exists      
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):                
            self.tod_enabled = True
        else:
            self.tod_enabled=False

        # warn if the network not available when ToD required

        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(self,'Network not connected  so Time of Day scheduler may be using the internal clock')

        # warn about start shows and scheduler

        if self.starter_show['start-show']=='' and self.tod_enabled is False:
            self.mon.sched(self,"No Start Shows in Start Show and no shows scheduled") 
            self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] !='' and self.tod_enabled is True:
            self.mon.sched(self,"Start Shows in Start Show and shows scheduled - conflict?") 
            self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()           

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod=TimeOfDay()
            self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command)
            self.tod.poll()            


        # start Tkinters event loop
        self.root.mainloop( )
Exemple #5
0
    def __init__(self):
        
        self.pipresents_issue="1.1"
        
        StopWatch.global_enable=False

#****************************************
# INTERPRET COMMAND LINE
# ***************************************

        self.options=command_options()
        

        pp_dir=sys.path[0]
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit()

        
        #Initialise logging
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        self.mon.on()
        if self.options['debug']==True:
            Monitor.global_enable=True
        else:
            Monitor.global_enable=False
 
        self.mon.log (self, "Pi Presents is starting")
        
        #self.show=None
        
        # create  profile  for pp_editor test files if already not there.
        if not os.path.exists(pp_dir+"/pp_home/pp_profiles/pp_editor"):
            self.mon.log(self,"Making pp_editor directory") 
            os.makedirs(pp_dir+"/pp_home/pp_profiles/pp_editor")
            
            
        #profile path from -p option
        if self.options['profile']<>"":
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"
        
       #get directory containing pp_home from the command,
        if self.options['home'] =="":
            home = os.path.expanduser('~')+ os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"
                   
        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home=pp_dir+"/pp_home"
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " " + str(i))
            if os.path.exists(home):
                self.mon.log(self,"Using pp_home at: " + home)
                self.pp_home=home
                break
            time.sleep (1)

        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile=self.pp_home+self.pp_profile_path
        if not os.path.exists(self.pp_profile):
            self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile"

        if self.options['verify']==True:
            val =Validator()
            if  val.validate_profile(None,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False:
                tkMessageBox.showwarning("Pi Presents","Validation Failed")
                exit()
                
        # open the resources
        self.rr=ResourceReader()
        # read the file, done once for all the other classes to use.
        self.rr.read(pp_dir,self.pp_home)

        
        #initialise the showlists and read the showlists
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self._end('error','showlist not found')

        if float(self.showlist.sissue())<>float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self._end('error','wrong version of profile')
 
        # get the starter show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self._end('error','start show not found')
            
# ********************
# SET UP THE GUI
# ********************

        #turn off the screenblanking and saver
        if self.options['noblank']==True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        # control display of window decorations
        if self.options['fullscreen']<>"partial":
            self.root = Tk(className="fspipresents")
            os.system('unclutter &')
        else:
              self.root = Tk(className="pipresents")          


        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')
        
        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions
        self.window_height=self.screen_height
        self.window_width=self.screen_width
        self.window_x=0
        self.window_y=0
        if self.options['fullscreen']<>"partial":
            bar=self.options['fullscreen']
            # allow just 2 pixels for the hidden taskbar
            if bar in ('left','right'):
                self.window_width=self.screen_width-2
            else:
                self.window_height=self.screen_height-2
            if bar =="left":
                self.window_x=2
            if bar =="top":
                self.window_y=2   
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')
        else:
            self.window_width=self.screen_width-200
            self.window_height=self.screen_height-200
            self.window_x=50
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
            


        
        #canvas covers the whole window
        self.canvas_height=self.window_height
        self.canvas_width=self.window_width
        
        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.on_break_key)

        # Always use CTRL-Break key to close the program as a get out of jail
        self.root.bind("<Break>",self.e_on_break_key)
        
        #pass all other keys along to 'shows' and hence to 'players'
        self.root.bind("<Escape>", self._escape_pressed)
        self.root.bind("<Up>", self._up_pressed)
        self.root.bind("<Down>", self._down_pressed)
        self.root.bind("<Return>", self._return_pressed)
        self.root.bind("<space>", self._pause_pressed)
        self.root.bind("p", self._pause_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height, width=self.canvas_width)
        #self.canvas.grid(row=1,columnspan=2)
        self.canvas.pack()
        # make sure focus is set on canvas.
        self.canvas.focus_set()


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        
        #kick off GPIO if enabled by command line option
        if self.options['gpio']==True:
            from pp_buttons import Buttons
            # initialise the buttons connected to GPIO
            self.Buttons=Buttons
            self.buttons = Buttons(self.root,20,self.button_pressed)
            self.buttons.poll()

            
        #  kick off the initial show            
        self.show=None
        # get the start show from the showlist
        index = self.showlist.index_of_show(self.starter_show['start-show'])
        if index >=0:
            self.showlist.select(index)
            self.start_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show not found in showlist: "+ self.starter_show['start-show'])
            self._end('error','show not found in showlist')
            
        if self.start_show['type']=="mediashow":
            self.show= MediaShow(self.start_show,
                                                            self.canvas,
                                                            self.showlist,
                                                            self.pp_home,
                                                            self.pp_profile)
            self.show.play(self._end_play_show,top=True,command='nil')
            self.root.mainloop( )     
            
        elif self.start_show['type']=="menu":
            self.show= MenuShow(self.start_show,
                                                    self.canvas,
                                                    self.showlist,
                                                    self.pp_home,
                                                    self.pp_profile)
            self.show.play(self._end_play_show,top=True,command='nil')
            self.root.mainloop( )

        elif self.start_show['type']=="liveshow":
            self.show= LiveShow(self.start_show,
                                                    self.canvas,
                                                    self.showlist,
                                                    self.pp_home,
                                                    self.pp_profile)
            self.show.play(self._end_play_show,top=True,command='nil')
            self.root.mainloop( )                 
            
        else:
            self.mon.err(self,"unknown mediashow type in start show - "+ self.start_show['type'])
            self._end('error','unknown mediashow type')
Exemple #6
0
 def validate_profile(self):
     val = Validator()
     val.validate_profile(self.root, self.pp_dir, self.pp_home_dir,
                          self.pp_profile_dir, self.editor_issue, True)
    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3"
        self.pipresents_minorissue = '1.3.1g'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner
        self.pp_background='black'

        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory:\n{0}".format(pp_dir))
            exit(103)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()
        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents', 'pp_paths',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'PathManager','ControlsManager','ShowManager','PluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver'
                            ]

        # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','OMXDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)    
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue)
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])
        if os.geteuid() !=0:
            user=os.getenv('USER')
        else:
            user = os.getenv('SUDO_USER')
        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.gpiodriver=None
        self.oscdriver=None
        self.osc_enabled=False
        self.gpio_enabled=False
        self.tod_enabled=False
         
        # get home path from -o option
        self.pp_home = pp_paths.get_home(self.options['home'])
        if self.pp_home is None:
            self.end('error','Failed to find pp_home')

        # get profile path from -p option
        # pp_profile is the full path to the directory that contains 
        # pp_showlist.json and other files for the profile
        self.pp_profile = pp_paths.get_profile_dir(self.pp_home, self.options['profile'])
        if self.pp_profile is None:
            self.end('error','Failed to find profile')

        # check profile exists
        if os.path.exists(self.pp_profile):
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error','Failed to find profile')

        self.mon.start_stats(self.options['profile'])
        
        # check 'verify' option
        if self.options['verify'] is True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is  False:
                self.mon.err(self,"Validation Failed")
                self.end('error','Validation Failed')

        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error','showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) != float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self.end('error','wrong version of profile')


        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error','start show not found')

        if self.starter_show['start-show']=='':
             self.mon.warn(self,"No Start Shows in Start Show")       

# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.pp_background)

        self.mon.log(self, 'native screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixcels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'commanded screen dimensions are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixcels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter 1>&- 2>&- &') # Suppress 'someone created a subwindow' complaints from unclutter
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvs cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.pp_background)


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False:
            self.end('error','cannot find or error in keys.cfg')
        kbd.bind_keys(self.root,self.handle_input_event)

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.exitpipresents_required=False
        
        # kick off GPIO if enabled by command line option
        self.gpio_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver=GPIODriver()
            reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.gpio_enabled=True
                # and start polling gpio
                self.gpiodriver.poll()
            
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
            self.oscdriver=OSCDriver()
            reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.osc_enabled=True
                self.root.after(1000,self.oscdriver.start_server())

        # and run the start shows
        self.run_start_shows()

       # set up the time of day scheduler including catchup         
        self.tod_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):
            # kick off the time of day scheduler which may run additional shows
            self.tod=TimeOfDay()
            self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command)
            self.tod_enabled = True


        # then start the time of day scheduler
        if self.tod_enabled is True:
            self.tod.poll()

        # start Tkinters event loop
        self.root.mainloop( )
Exemple #8
0
    def __init__(self):

        self.pipresents_issue = "1.2"
        self.pipresents_minorissue = '1.2.3f'
        self.nonfull_window_width = 0.5  # proportion of width
        self.nonfull_window_height = 0.6  # proportion of height
        self.nonfull_window_x = 0  # position of top left corner
        self.nonfull_window_y = 0  # position of top left corner

        StopWatch.global_enable = False

        #****************************************
        # Initialisation
        # ***************************************
        # get command line options
        self.options = command_options()

        # get pi presents code directory
        pp_dir = sys.path[0]
        self.pp_dir = pp_dir

        if not os.path.exists(pp_dir + "/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents",
                                     "Bad Application Directory")
            exit()

        #Initialise logging
        Monitor.log_path = pp_dir
        self.mon = Monitor()
        self.mon.on()
        # 0  - errors only
        # 1  - errors and warnings
        # 2  - everything
        if self.options['debug'] == True:
            Monitor.global_enable = 2
        else:
            Monitor.global_enable = 0

        # UNCOMMENT THIS TO LOG WARNINGS AND ERRORS ONLY
        # Monitor.global_enable=1

        self.mon.log(
            self,
            "\n\n\n\n\n*****************\nPi Presents is starting, Version:" +
            self.pipresents_minorissue)
        self.mon.log(self, "Version: " + self.pipresents_minorissue)
        self.mon.log(self, " OS and separator:" + os.name + '  ' + os.sep)
        self.mon.log(self, "sys.path[0] -  location of code: " + sys.path[0])
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME'))
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))

        # optional other classes used
        self.ppio = None
        self.tod = None

        #get profile path from -p option
        if self.options['profile'] <> "":
            self.pp_profile_path = "/pp_profiles/" + self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"

    #get directory containing pp_home from the command,
        if self.options['home'] == "":
            home = os.path.expanduser('~') + os.sep + "pp_home"
        else:
            home = self.options['home'] + os.sep + "pp_home"
        self.mon.log(self, "pp_home directory is: " + home)

        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home = pp_dir + "/pp_home"
        found = False
        for i in range(1, 10):
            self.mon.log(self,
                         "Trying pp_home at: " + home + " (" + str(i) + ')')
            if os.path.exists(home):
                found = True
                self.pp_home = home
                break
            time.sleep(1)
        if found == True:
            self.mon.log(
                self,
                "Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.log(
                self,
                "FAILED to find requested home directory, using default to display error message: "
                + self.pp_home)

        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile = self.pp_home + self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.log(
                self, "Found Requested profile - pp_profile directory is: " +
                self.pp_profile)
        else:
            self.pp_profile = pp_dir + "/pp_home/pp_profiles/pp_profile"
            self.mon.log(
                self,
                "FAILED to find requested profile, using default to display error message: pp_profile"
            )

        if self.options['verify'] == True:
            val = Validator()
            if val.validate_profile(None, pp_dir, self.pp_home,
                                    self.pp_profile, self.pipresents_issue,
                                    False) == False:
                tkMessageBox.showwarning("Pi Presents", "Validation Failed")
                exit()

        # open the resources
        self.rr = ResourceReader()
        # read the file, done once for all the other classes to use.
        if self.rr.read(pp_dir, self.pp_home, self.pp_profile) == False:
            self.end('error', 'cannot find resources.cfg')

        #initialise and read the showlist in the profile
        self.showlist = ShowList()
        self.showlist_file = self.pp_profile + "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self, "showlist not found at " + self.showlist_file)
            self.end('error', 'showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) <> float(self.pipresents_issue):
            self.mon.err(
                self, "Version of profile " + self.showlist.sissue() +
                " is not  same as Pi Presents, must exit")
            self.end('error', 'wrong version of profile')

        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >= 0:
            self.showlist.select(index)
            self.starter_show = self.showlist.selected_show()
        else:
            self.mon.err(self, "Show [start] not found in showlist")
            self.end('error', 'start show not found')

# ********************
# SET UP THE GUI
# ********************
#turn off the screenblanking and saver
        if self.options['noblank'] == True:
            call(["xset", "s", "off"])
            call(["xset", "s", "-dpms"])

        self.root = Tk()

        self.title = 'Pi Presents - ' + self.pp_profile
        self.icon_text = 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')

        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions and decorations
        if self.options['fullscreen'] == True:

            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_width = self.screen_width
            self.window_height = self.screen_height
            self.window_x = 0
            self.window_y = 0
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))
            self.root.attributes('-zoomed', '1')
        else:
            self.window_width = int(self.screen_width *
                                    self.nonfull_window_width)
            self.window_height = int(self.screen_height *
                                     self.nonfull_window_height)
            self.window_x = self.nonfull_window_x
            self.window_y = self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))

        #canvas covers the whole window
        self.canvas_height = self.screen_height
        self.canvas_width = self.screen_width

        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol("WM_DELETE_WINDOW", self.exit_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height,
                           width=self.canvas_width,
                           highlightthickness=0)
        # self.canvas.pack()
        self.canvas.place(x=0, y=0)

        self.canvas.focus_set()

        # ****************************************
        # INITIALISE THE INPUT DRIVERS
        # ****************************************

        # looks after bindings between symbolic names and internal operations
        controlsmanager = ControlsManager()
        if controlsmanager.read(pp_dir, self.pp_home,
                                self.pp_profile) == False:
            self.end('error', 'cannot find or error in controls.cfg.cfg')
        else:
            controlsmanager.parse_defaults()

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd = KbdDriver()
        if kbd.read(pp_dir, self.pp_home, self.pp_profile) == False:
            self.end('error', 'cannot find or error in keys.cfg')
        kbd.bind_keys(self.root, self.input_pressed)

        self.sr = ScreenDriver()
        # read the screen click area config file
        if self.sr.read(pp_dir, self.pp_home, self.pp_profile) == False:
            self.end('error', 'cannot find screen.cfg')

        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        reason, message = self.sr.make_click_areas(self.canvas,
                                                   self.input_pressed)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required = False

        #kick off GPIO if enabled by command line option
        if self.options['gpio'] == True:
            from pp_gpio import PPIO
            # initialise the GPIO
            self.ppio = PPIO()
            # PPIO.gpio_enabled=False
            if self.ppio.init(pp_dir, self.pp_home, self.pp_profile,
                              self.canvas, 50, self.gpio_pressed) == False:
                self.end('error', 'gpio error')

            # and start polling gpio
            self.ppio.poll()

        #kick off the time of day scheduler
        self.tod = TimeOfDay()
        self.tod.init(pp_dir, self.pp_home, self.canvas, 500)
        self.tod.poll()

        # Create list of start shows initialise them and then run them
        self.run_start_shows()

        #start tkinter
        self.root.mainloop()
    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES
                     | gc.DEBUG_OBJECTS | gc.DEBUG_SAVEALL)
        self.pipresents_issue = "1.3"
        self.pipresents_minorissue = '1.3.1g'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45  # proportion of width
        self.nonfull_window_height = 0.7  # proportion of height
        self.nonfull_window_x = 0  # position of top left corner
        self.nonfull_window_y = 0  # position of top left corner
        self.pp_background = 'black'

        StopWatch.global_enable = False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM, self.handle_sigterm)

        # ****************************************
        # Initialisation
        # ***************************************
        # get command line options
        self.options = command_options()

        # get Pi Presents code directory
        pp_dir = sys.path[0]
        self.pp_dir = pp_dir

        if not os.path.exists(pp_dir + "/pipresents.py"):
            if self.options['manager'] is False:
                tkMessageBox.showwarning(
                    "Pi Presents",
                    "Bad Application Directory:\n{0}".format(pp_dir))
            exit(103)

        # Initialise logging and tracing
        Monitor.log_path = pp_dir
        self.mon = Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes = [
            'PiPresents', 'pp_paths', 'HyperlinkShow', 'RadioButtonShow',
            'ArtLiveShow', 'ArtMediaShow', 'MediaShow', 'LiveShow', 'MenuShow',
            'PathManager', 'ControlsManager', 'ShowManager', 'PluginManager',
            'MplayerDriver', 'OMXDriver', 'UZBLDriver', 'KbdDriver',
            'GPIODriver', 'TimeOfDay', 'ScreenDriver', 'Animate', 'OSCDriver'
        ]

        # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','OMXDriver']

        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.log(
            self,
            "Pi Presents is starting, Version:" + self.pipresents_minorissue)
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self, "sys.path[0] -  location of code: " + sys.path[0])
        if os.geteuid() != 0:
            user = os.getenv('USER')
        else:
            user = os.getenv('SUDO_USER')
        self.mon.log(self, 'User is: ' + user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # optional other classes used
        self.root = None
        self.ppio = None
        self.tod = None
        self.animate = None
        self.gpiodriver = None
        self.oscdriver = None
        self.osc_enabled = False
        self.gpio_enabled = False
        self.tod_enabled = False

        # get home path from -o option
        self.pp_home = pp_paths.get_home(self.options['home'])
        if self.pp_home is None:
            self.end('error', 'Failed to find pp_home')

        # get profile path from -p option
        # pp_profile is the full path to the directory that contains
        # pp_showlist.json and other files for the profile
        self.pp_profile = pp_paths.get_profile_dir(self.pp_home,
                                                   self.options['profile'])
        if self.pp_profile is None:
            self.end('error', 'Failed to find profile')

        # check profile exists
        if os.path.exists(self.pp_profile):
            self.mon.log(
                self, "Found Requested profile - pp_profile directory is: " +
                self.pp_profile)
        else:
            self.mon.err(
                self, "Failed to find requested profile: " + self.pp_profile)
            self.end('error', 'Failed to find profile')

        self.mon.start_stats(self.options['profile'])

        # check 'verify' option
        if self.options['verify'] is True:
            val = Validator()
            if val.validate_profile(None, pp_dir, self.pp_home,
                                    self.pp_profile, self.pipresents_issue,
                                    False) is False:
                self.mon.err(self, "Validation Failed")
                self.end('error', 'Validation Failed')

        # initialise and read the showlist in the profile
        self.showlist = ShowList()
        self.showlist_file = self.pp_profile + "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self, "showlist not found at " + self.showlist_file)
            self.end('error', 'showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) != float(self.pipresents_issue):
            self.mon.err(
                self, "Version of profile " + self.showlist.sissue() +
                " is not  same as Pi Presents, must exit")
            self.end('error', 'wrong version of profile')

        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >= 0:
            self.showlist.select(index)
            self.starter_show = self.showlist.selected_show()
        else:
            self.mon.err(self, "Show [start] not found in showlist")
            self.end('error', 'start show not found')

        if self.starter_show['start-show'] == '':
            self.mon.warn(self, "No Start Shows in Start Show")

# ********************
# SET UP THE GUI
# ********************
# turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset", "s", "off"])
            call(["xset", "s", "-dpms"])

        self.root = Tk()

        self.title = 'Pi Presents - ' + self.pp_profile
        self.icon_text = 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.pp_background)

        self.mon.log(
            self, 'native screen dimensions are ' +
            str(self.root.winfo_screenwidth()) + ' x ' +
            str(self.root.winfo_screenheight()) + ' pixcels')
        if self.options['screensize'] == '':
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason, message, self.screen_width, self.screen_height = self.parse_screen(
                self.options['screensize'])
            if reason == 'error':
                self.mon.err(self, message)
                self.end('error', message)

        self.mon.log(
            self, 'commanded screen dimensions are ' + str(self.screen_width) +
            ' x ' + str(self.screen_height) + ' pixcels')

        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width = int(self.root.winfo_screenwidth() *
                                    self.nonfull_window_width)
            self.window_height = int(self.root.winfo_screenheight() *
                                     self.nonfull_window_height)
            self.window_x = self.nonfull_window_x
            self.window_y = self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))
        else:
            self.window_width = self.screen_width
            self.window_height = self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system(
                'unclutter 1>&- 2>&- &'
            )  # Suppress 'someone created a subwindow' complaints from unclutter
            self.window_x = 0
            self.window_y = 0
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))
            self.root.attributes('-zoomed', '1')

        # canvs cover the whole screen whatever the size of the window.
        self.canvas_height = self.screen_height
        self.canvas_width = self.screen_width

        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.pp_background)

        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=1,
                               highlightcolor='yellow')

        self.canvas.place(x=0, y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()

        # ****************************************
        # INITIALISE THE INPUT DRIVERS
        # ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd = KbdDriver()
        if kbd.read(pp_dir, self.pp_home, self.pp_profile) is False:
            self.end('error', 'cannot find or error in keys.cfg')
        kbd.bind_keys(self.root, self.handle_input_event)

        self.sr = ScreenDriver()
        # read the screen click area config file
        reason, message = self.sr.read(pp_dir, self.pp_home, self.pp_profile)
        if reason == 'error':
            self.end('error', 'cannot find screen.cfg')

        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason, message = self.sr.make_click_areas(self.canvas,
                                                   self.handle_input_event)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required = False
        self.exitpipresents_required = False

        # kick off GPIO if enabled by command line option
        self.gpio_enabled = False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' + os.sep +
                          'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver = GPIODriver()
            reason, message = self.gpiodriver.init(pp_dir, self.pp_home,
                                                   self.pp_profile,
                                                   self.canvas, 50,
                                                   self.handle_input_event)
            if reason == 'error':
                self.end('error', message)
            else:
                self.gpio_enabled = True
                # and start polling gpio
                self.gpiodriver.poll()

        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir, self.pp_home, self.pp_profile, self.canvas,
                          200, self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id = -1
        self.show_manager = ShowManager(show_id, self.showlist,
                                        self.starter_show, self.root,
                                        self.canvas, self.pp_dir,
                                        self.pp_profile, self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas, self.all_shows_ended_callback,
                               self.handle_command, self.showlist)
        # Register all the shows in the showlist
        reason, message = self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled = False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' + os.sep +
                          'osc.cfg'):
            self.oscdriver = OSCDriver()
            reason, message = self.oscdriver.init(
                self.pp_profile, self.handle_command, self.handle_input_event,
                self.e_osc_handle_output_event)
            if reason == 'error':
                self.end('error', message)
            else:
                self.osc_enabled = True
                self.root.after(1000, self.oscdriver.start_server())

        # and run the start shows
        self.run_start_shows()

        # set up the time of day scheduler including catchup
        self.tod_enabled = False
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):
            # kick off the time of day scheduler which may run additional shows
            self.tod = TimeOfDay()
            self.tod.init(pp_dir, self.pp_home, self.pp_profile, self.root,
                          self.handle_command)
            self.tod_enabled = True

        # then start the time of day scheduler
        if self.tod_enabled is True:
            self.tod.poll()

        # start Tkinters event loop
        self.root.mainloop()
class EditItem(ttkSimpleDialog.Dialog):
    def __init__(self, tkparent, title, objtype, field_content, show_refs):
        self.mon = Monitor()

        if objtype == SHOW:
            self.record_specs = PPdefinitions.show_types
            self.field_specs = PPdefinitions.show_field_specs
            self.initial_tab = "show"
        elif objtype == TRACK:
            self.record_specs = PPdefinitions.track_types
            self.field_specs = PPdefinitions.track_field_specs
            self.initial_tab = "track"

        # save the extra arg to instance variable
        self.objtype = objtype
        self.field_content = field_content  # dictionary - the track parameters to be edited
        self.show_refs = show_refs
        self.show_refs.append("")
        self.initial_media_dir = pp_paths.media_dir
        self.pp_home_dir = pp_paths.pp_home

        # list of stringvars from which to get edited values (for optionmenu only??)
        self.entries = []

        # lookup for widget when all we have is the field
        self.widgets = {}

        self.validator = Validator()
        self.validator.initialize(scope=objtype)
        if objtype == SHOW:
            self.mon.log(self, "Editing show '{0} -------------------".format(Validator.get_showref))
            self.validator.set_current(show=field_content)
        elif objtype == TRACK:
            self.mon.log(self, "Editing track '{0} -------------------".format(Validator.get_trackref))
            self.validator.set_current(track=field_content)
        self.tempcontent = copy.deepcopy(self.field_content)

        # and call the base class _init_which calls body immeadiately and apply on OK pressed
        ttkSimpleDialog.Dialog.__init__(self, tkparent, title)

    def body(self, root):
        self.root = root
        bar = TabBar(root, init_name=self.initial_tab)
        self.bar = bar
        bar.bind("<<NotebookTabChanged>>", self.tab_changed)

        self.photo_critical = pp_utils.load_png("critical")
        self.photo_error = pp_utils.load_png("error")
        self.photo_warning = pp_utils.load_png("warning")
        self.photo_check = pp_utils.load_png("check")
        self.photo_spacer = pp_utils.load_gif("spacer")

        self.body_fields(root, bar)
        # bar.config(bd=1, relief=RIDGE)   # add some border
        bar.show()

    def body_fields(self, master, bar):
        # get fields for this record using the record type in the loaded record
        record_fields = self.record_specs[self.field_content["type"]]
        # print "Generating fields: {0} record_fields for {1}".format(len(record_fields), self.field_content['type'])

        # init results of building the form
        self.tab_row = 1  # row on form
        self.fields = []  # generated by body_fields - list of field objects in record  fields order, not for sep or tab
        self.entries = (
            []
        )  # generated by body_fields - list of stringvars in record fields order, used option-menus only
        # populate the dialog box using the record fields to determine the order
        for field in record_fields:
            # print "BODY_FIELDS: {0}, {1}".format(field, len(record_fields))
            # get list of values where required
            values = []
            if self.field_specs[field]["shape"] in ("option-menu", "spinbox"):
                # print 'should be field name', field
                # print 'should be shape',self.field_specs[field]['shape']
                if field in ("sub-show", "start-show"):
                    values = self.show_refs
                else:
                    values = self.field_specs[field]["values"]
            else:
                values = []
            # make the entry
            obj = self.make_entry(master, field, self.field_specs[field], values, bar)
            if obj is not None:
                # print "Making entry row {0}: {1}".format(len(self.fields), field, self.field_specs[field])
                self.fields.append(obj)
        return None  # No initial focus

    # create an entry in a dialog box
    def make_entry(self, master, field, field_spec, values, bar):
        # print 'make entry',len(self.fields),field,field_spec
        if field_spec["shape"] == "tab":
            self.current_tab = Tab(master, field_spec["name"])
            bar.add(self.current_tab, field_spec["text"], sticky="news", compound="left")
            self.tab_row = 1
            self.current_tab.errors = []
            self.current_tab.warnings = []
            return None
        elif field_spec["shape"] == "sep":
            ttkLabel(self.current_tab, text="", anchor=W).grid(row=self.tab_row, column=0, sticky=W)
            self.tab_row += 1
            return None

        else:

            # print 'replace param in make entry',field
            # print 'content', field, self.field_content[field]
            # is it in the field content dictionary
            if field not in self.field_content:
                self.mon.log(self, "Value for field not found in opened file: " + field)
                return None
            else:
                # write the label
                ttkLabel(self.current_tab, text=field_spec["text"], anchor=W).grid(row=self.tab_row, column=0, sticky=W)

                # make the editable field
                if field_spec["shape"] in ("entry", "colour", "browse", "font"):
                    obj = ttkEntry(self.current_tab, width=40, font="arial 11")
                    obj.insert(END, self.field_content[field])
                    self.add_validation(obj, field)

                elif field_spec["shape"] == "text":
                    obj = ttkScrolledText(self.current_tab, height=8, width=40, font="arial 11")
                    obj.insert(END, self.field_content[field])
                    self.add_validation(obj, field)
                    obj.bind("<Tab>", self.tab)

                elif field_spec["shape"] == "spinbox":
                    obj = ttk.Combobox(master, height=10, textvariable=self._family)
                    obj.grid(row=theRow, column=0, columnspan=2, sticky=N + S + E + W, padx=10)
                    # obj=Spinbox(self.current_tab,values=values,wrap=True)
                    obj.insert(END, self.field_content[field])
                    self.add_validation(obj, field)
                    obj.bind("<<ComboboxSelected>>", self.e_validate_widget)

                elif field_spec["shape"] == "option-menu":
                    self.option_val = StringVar(self.current_tab)
                    self.option_val.set(self.field_content[field])
                    obj = ttkCombobox(self.current_tab, textvariable=self.option_val)
                    obj["values"] = values
                    self.entries.append(self.option_val)
                    self.add_validation(obj, field)
                    obj.bind("<<ComboboxSelected>>", self.e_validate_widget)

                else:
                    self.mon.log(self, "Uknown shape for: " + field)
                    return None

                # Read-only items
                if field_spec["read-only"] == "yes":
                    obj.config(state=DISABLED)
                # Required-entry items
                elif field_spec["must"] == "yes":
                    print "Found 'must' in {0}".format(field)
                    # obj.configure(background='lemon chiffon') # reserve pink for validation errors (future)?
                    obj.configure(style="required.T" + obj.winfo_name().replace("ttk", "T"))
                elif field_spec["shape"] == "text":
                    s = ttk.Style()
                    bg = s.lookup("TEntry", option="fieldbackground")
                    obj.config(background=bg)

                obj.grid(row=self.tab_row, column=2, sticky=W)

                # display buttons where required
                if field_spec["shape"] == "browse":
                    but = ttkButton(
                        self.current_tab, width=1, height=1, bg="dark grey", command=(lambda o=obj: self.browse(o))
                    )
                    but.grid(row=self.tab_row, column=3, sticky=W)

                elif field_spec["shape"] == "colour":
                    but = ttkButton(
                        self.current_tab, width=1, height=1, bg="dark grey", command=(lambda o=obj: self.pick_colour(o))
                    )
                    but.grid(row=self.tab_row, column=3, sticky=W)

                elif field_spec["shape"] == "font":
                    but = ttkButton(
                        self.current_tab, width=1, height=1, bg="dark grey", command=(lambda o=obj: self.pick_font(o))
                    )
                    but.grid(row=self.tab_row, column=3, sticky=W)

                self.tab_row += 1
                return obj

    def add_validation(self, widget, field):
        widget.bind("<FocusOut>", self.validate_field_focusout)
        # use key validation to get the new value after a keypress (KeyPress/Release events give the 'before' value)
        if isinstance(widget, tk.Entry):
            cmd = (self.register(self.validate_field_keypress), "%P", "%W")
            widget["validate"] = "key"
            widget["validatecommand"] = cmd
        icon = ttkLabel(self.current_tab, text="", anchor=W, padding=[10, 0, 0, 0])
        icon.grid(row=self.tab_row, column=1, sticky=W)
        ttkToolTip.createtip(icon, "")
        widget.field = field
        widget.icon = icon
        widget.tab = self.current_tab
        self.tempcontent[field] = self.field_content[field]
        self.widgets[field] = widget
        self.validate_widget(widget)

    def validate_field_keypress(self, p, w):
        widget = self.root.nametowidget(w)
        self.validate_widget(widget, value=p)
        return True

    def validate_field_focusout(self, event):
        w = event.widget
        self.check_tab_errors(w.tab)

    def tab(self, event):
        event.widget.tk_focusNext().focus()
        return "break"

    def keypressed(self, event):
        try:
            w = event.widget
            self.check_tab_errors(w.tab)
        except AttributeError:
            pass

    def e_validate_widget(self, event):
        self.validate_widget(event.widget)

    def validate_widget(self, widget, value=None):
        w = widget
        try:
            icon = w.icon
        except AttributeError:
            print "This widget doesn't have validation set up: ", w.__class__.__name__
            return
        # try:
        # self.mon.log(self, "Validation set up: ", w.__class__.__name__, w.field)
        if value is None:
            if isinstance(w, (tk.Text, ScrolledText)):
                value = w.get(1.0, END)
            else:
                value = w.get()
        self.tempcontent[w.field] = value
        # tab = self.bar.tab(w.tab)
        tab = w.tab
        result = self.validator.validate_widget(self.objtype, self.tempcontent, w.field)
        self.style_widget_background(w, result)
        if result.passed is False:
            # self.mon.log(self, "Error found: ", result.message)
            if result.severity == ERROR:
                icon["image"] = self.photo_error
                if w not in tab.errors:
                    tab.errors.append(w)
            elif result.severity == WARNING:
                icon["image"] = self.photo_warning
                if w not in tab.warnings:
                    tab.warnings.append(w)
            icon.tip.settip(result.message)
            icon.grid()  # redisplay
        else:
            # print "Result for '", w.field, "': passed=", result.passed, ", blank=", result.blank
            if w in tab.errors:
                tab.errors.remove(w)
            if w in tab.warnings:
                tab.warnings.remove(w)
            icon["image"] = self.photo_spacer
            icon.grid()
            # icon.grid_remove()  # hide image
            icon.tip.cleartip()
        self.style_tab(tab)
        if result.dependents is not None:
            # self.mon.log(self, "result has dependents")
            for dep in result.dependents:
                if dep in self.widgets:
                    self.mon.log(self, "Processing dependent: ", dep, "--------------")
                    if self.validate_widget(self.widgets[dep]) is False:
                        return False
        return result.passed
        # except Exception as e:
        #    print "Validation failed for ", w.field, ": ", e
        return False

    def style_tab(self, tab):
        if len(tab.errors) > 0:
            self.bar.tab(tab, image=self.photo_error, compound="left")
        elif len(tab.warnings) > 0:
            self.bar.tab(tab, image=self.photo_warning, compound="left")
        else:
            self.bar.tab(tab, compound="text")

    def style_widget_background(self, widget, result):
        s = ttk.Style()
        w = widget
        name = w.__class__.__name__.replace("ttk", "")
        name = "T" + name
        if result.passed is False:
            if name in ("TScrolledText"):
                # set background directly
                if result.severity == ERROR:
                    bg = tkconversions.ERROR_COLOR
                else:
                    bg = tkconversions.WARNING_COLOR
                w.configure(background=bg)
            else:
                # use the style that has the background defined
                if result.severity == ERROR:
                    w.configure(style="error." + name)
                else:
                    w.configure(style="warning." + name)
        else:
            if name in ("TScrolledText"):
                w.configure(background=s.lookup("TEntry", option="fieldbackground"))
            else:
                w.configure(style=name)

    def check_tab_errors(self, tab):
        errors = 0
        warnings = 0
        # for w in self.tab_children[tab]:
        for w in tab.errors:
            result = self.validate_widget(w)
            if result is False:
                errors += 1
        for w in tab.warnings:
            result = self.validate_widget(w)
            if result is False:
                warnings += 1
        if errors > 0:
            self.bar.tab(w.tab, image=self.photo_error, compound="left")
        elif warnings > 0:
            self.bar.tab(w.tab, image=self.photo_warning, compound="left")
        else:
            self.bar.tab(tab, compound="text")

    def tab_changed(self, event):
        bar = event.widget
        tabname = bar.select()
        tab = self.root.nametowidget(tabname)
        self.check_tab_errors(tab)

    def apply(self):
        # get list of fields in the record in the same order as the form was generated
        record_fields = self.record_specs[self.field_content["type"]]

        field_index = 0  # index to self.fields - not incremented for tab and sep
        entry_index = 0  # index of stringvars for option_menu
        # print "-------------------------------------"

        for field in record_fields:
            # print field
            # get the details of this field
            field_spec = self.field_specs[field]
            # print  'reading row',field_index,field_spec['shape']

            # and get the value
            if field_spec["shape"] not in ("sep", "tab"):
                # print "Applying row {0} '{1}' ({2}, {3}). {4} entries".format(field_index, field, self.field_content[field],
                #    field_spec['shape'], len(self.entries))

                if field_spec["shape"] == "text":
                    # print "Applying ", field
                    self.field_content[field] = self.fields[field_index].get(1.0, END).rstrip("\n")

                elif field_spec["shape"] == "option-menu":
                    self.field_content[field] = self.entries[entry_index].get()
                    entry_index += 1
                else:
                    self.field_content[field] = self.fields[field_index].get().strip()

                # print self.field_content[field]
                field_index += 1

        self.result = True
        return self.result

    def pick_colour(self, obj):
        rgb, colour = askcolor()
        # print rgb,colour
        if colour is not None:
            obj.delete(0, END)
            obj.insert(END, colour)

    def pick_font(self, obj):
        font = askChooseFont(self.root)
        # print font
        if font is not None:
            obj.delete(0, END)
            obj.insert(END, font)

    def browse(self, obj):
        # print "initial directory ", self.options.initial_media_dir
        file_path = tkFileDialog.askopenfilename(initialdir=self.initial_media_dir, multiple=False)
        if file_path == "":
            return
        file_path = os.path.normpath(file_path)
        # print "file path ", file_path
        relpath = os.path.relpath(file_path, self.pp_home_dir)
        # print "relative path ",relpath
        common = os.path.commonprefix([file_path, self.pp_home_dir])
        # print "common ",common
        if common.endswith("pp_home") is False:
            obj.delete(0, END)
            obj.insert(END, file_path)
        else:
            location = "+" + os.sep + relpath
            location = string.replace(location, "\\", "/")
            # print "location ",location
            obj.delete(0, END)
            obj.insert(END, location)

    def kpenter_keypressed(self, event=None):
        # need to insert newline programmatically
        ctl = self.focus_get()
        if isinstance(ctl, Text) or isinstance(ctl, ScrolledText):
            event.widget.insert(INSERT, "\n")
            return "break"
        else:
            return self.ok(event)

    def enter_keypressed(self, event=None):
        """ Check what control has focus. If it's not a text widget, close (accept) the window.
            (Enter key is needed by text widget to enter a new line)
            The dropdown widget handles and consumes enter key to close its dropdown (accept).
        """
        ctl = self.focus_get()
        if isinstance(ctl, Text) or isinstance(ctl, ScrolledText):
            return
        else:
            # return ttkSimpleDialog.Dialog.ok(self, event)
            return self.ok(event)

    def escape_keypressed(self, event=None):
        """ Check what control has focus. If it's not a text widget, close (cancel) the window.
            (Actually, I'm not sure what the issue is with the text widget with regards to the
            Escape key, so we'll comment out the special handling for now.)
            The dropdown widget handles and consumes escape key to close its dropdown (cancel).
        """
        # ctl = self.focus_get()
        # if isinstance(ctl, Text) or isinstance(ctl, ScrolledText):
        #    return
        # else:
        #    return self.cancel()
        return self.cancel(event)

    def buttonbox(self):
        """add modified button box.
        This function was originally here to 'override the standard [buttonbox] to get rid of 
        key bindings which cause trouble with text widget'.
        My opinion (drewkeller) is Escape-to-close and Enter-to-accept are the standard GUI 
        expectations of a dialog like this, so they would be correct.
        Issues with the text widget can be handled in keypress handlers.
        """
        box = ttkFrame(self)
        w = ttkButton(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = ttkButton(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<KP_Enter>", self.kpenter_keypressed)  # Enter on num pad
        self.bind("<Return>", self.enter_keypressed)
        self.bind("<Escape>", self.escape_keypressed)
        self.bind("<Key>", self.keypressed)
        box.pack()