class PiPresents:

    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( )



# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        #start show manager
        show_id=-1 #start show
        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 so empty show register and set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.all_shows_ended_callback)

        #parse the start shows field and start the initial shows       
        start_shows_text=self.starter_show['start-show']
        self.show_manager.start_initial_shows(start_shows_text)

    #callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message,force_shutdown):
        self.mon.log(self,"All shows ended, so terminate Pi Presents")
        if force_shutdown==True:
            self.shutdown_required=True
            self.mon.log(self,"shutdown forced by profile")  
            self.terminate('killed')
        else:
            self.end(reason,message)


# *********************
# User inputs
# ********************

    #gpio callback - symbol provided by gpio
    def gpio_pressed(self,index,symbol,edge):
        self.mon.log(self, "GPIO Pressed: "+ symbol)
        self.input_pressed(symbol,edge,'gpio')


    
    # all input events call this callback with a symbolic name.              
    def input_pressed(self,symbol,edge,source):
        self.mon.log(self,"input received: "+symbol)
        if symbol=='pp-exit':
            self.exit_pressed()
        elif symbol=='pp-shutdown':
            self.shutdown_pressed('delay')
        elif symbol=='pp-shutdownnow':
            self.shutdown_pressed('now')
        else:
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj<>None:
                    show_obj.input_pressed(symbol,edge,source)


# **************************************
# respond to exit inputs by terminating
# **************************************

    def shutdown_pressed(self, when):
        if when=='delay':
            self.root.after(5000,self.on_shutdown_delay)
        else:
            self.shutdown_required=True
            self.exit_pressed()           

    def on_shutdown_delay(self):
        if self.ppio.shutdown_pressed():
            self.shutdown_required=True
            self.exit_pressed()

         
    def exit_pressed(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players     
        self.mon.log(self,"kill sent to shows")   
        self.terminate('killed')


     # kill or error
    def terminate(self,reason):
        needs_termination=False
        for show in self.show_manager.shows:
            if show[ShowManager.SHOW_OBJ]<>None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                show[ShowManager.SHOW_OBJ].terminate(reason)
        if needs_termination==False:
            self.end(reason,'terminate - no termination of lower levels required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with message: " + reason + ' ' + message)
        if reason=='error':
            self.tidy_up()
            self.mon.log(self, "exiting because of error")
            #close logging files 
            self.mon.finish()
            exit()            
        else:
            self.tidy_up()
            self.mon.log(self,"no error - exiting normally")
            #close logging files 
            self.mon.finish()
            if self.shutdown_required==True:
                call(['sudo', 'shutdown', '-h', '-t 5','now'])
                exit()
            else:
                exit()


    
    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank']==True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up gpio
        if self.options['gpio']==True and self.ppio<>None:
            self.ppio.terminate()
            
        #tidy up time of day scheduler
        if self.tod<>None:
            self.tod.terminate()



# *****************************
# utilitities
# ****************************

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value
class Test:
    def __init__(self, track, pp_dir, pp_home, show_params, ct):

        self.track = track
        self.show_params = show_params
        self.pp_home = pp_home
        self.ct = ct
        self.break_from_loop = False

        # create and instance of a Tkinter top level window and refer to it as 'my_window'
        my_window = Tk()
        my_window.title("AudioPlayer Test Harness")

        # change the look of the window
        my_window.configure(background="grey")
        window_width = 1500
        window_height = 900

        canvas_height = window_height
        canvas_width = window_width

        # defne response to main window closing
        my_window.protocol("WM_DELETE_WINDOW", self.terminate)

        my_window.geometry("%dx%d+200+20" % (window_width, window_height))

        # Always use CTRL-Break key to close the program as a get out of jail
        my_window.bind("<Break>", self.e_terminate)

        my_window.bind("s", self.play_event)
        my_window.bind("p", self.pause_event)
        my_window.bind("q", self.stop_event)
        my_window.bind("l", self.loop_event)
        my_window.bind("n", self.next_event)

        # setup a canvas onto which will not be drawn the video!!
        canvas = Canvas(my_window, bg="black")
        canvas.config(height=canvas_height, width=canvas_width)
        canvas.pack()
        # make sure focus is set on canvas.
        canvas.focus_set()
        self.canvas = canvas

        # create an instance of PPIO and start polling
        self.ppio = PPIO()
        self.ppio.init(pp_dir, pp_home, self.canvas, 50, self.callback)
        self.ppio.poll()

        my_window.mainloop()

    def callback(self, index, name, edge):
        print name, edge

    # key presses

    def e_terminate(self, event):
        self.terminate()

    def play_event(self, event):
        self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params, self.ct)
        self.vp.play(
            self.track, self.on_end, self.do_ready, False, self.do_starting, self.do_playing, self.do_finishing
        )

    # toggles pause
    def pause_event(self, event):
        self.vp.key_pressed("p")

    def stop_event(self, event):
        self.break_from_loop = True
        self.vp.key_pressed("escape")

    def loop_event(self, event):
        # just kick off the first track, callback decides what to do next
        self.break_from_loop = False
        self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params)
        self.vp.play(
            self.track, self.what_next, self, do_ready, False, self.do_starting, self.do_playing, self.do_finishing
        )

    def next_event(self, event):
        self.break_from_loop = False
        self.vp.key_pressed("down")

    def what_next(self, reason, message):
        self.vp = None
        if reason in ("killed", "error"):
            self.end(reason, message)
        else:
            if self.break_from_loop == True:
                self.break_from_loop = False
                print "test harness: loop interupted"
                return
            else:
                self.vp = AudioPlayer(self.canvas, self.show_params)
                self.vp.play(self.track, self.what_next, self.do_starting, self.do_playing, self.do_finishing)

    def on_end(self, reason, message):
        self.vp = None
        print "Test Class: callback from AudioPlayer says: " + message
        if reason in ("killed", "error"):
            self.end(reason, message)
        else:
            return

    def do_ready(self):
        print "test class message from AudioPlayer: ready to play"
        return

    def do_starting(self):
        print "test class message from AudioPlayer: do starting"
        return

    def do_playing(self):
        # self.display_time.set(self.time_string(self.vp.audio_position))
        # print "test class message from AudioPlayer: do playing"
        return

    def do_finishing(self):
        print "test class message from AudioPlayer: do ending"
        return

    def terminate(self):
        if self.vp == None:
            self.end("killled", "killled")
        else:
            self.vp.terminate("killed")
            return

    def _end(self, reason, message):
        self.ppio.terminate()
        exit()
class PiPresents:

    # Constants for list of start shows
    SHOW_TEMPLATE=['',None,-1]
    NAME = 0   # text name of the show
    SHOW = 1   # the show object
    ID = 2    # Numeic identity of the show object, sent to the instance and returned in callbacks
    
    def __init__(self):
        
        self.pipresents_issue="1.2"
        
        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.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('~'))
        
        self.ppio=None
        self.tod=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"
            
        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"
        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,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)==False:
            #self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self._end('error','cannot find resources.cfg')            

        
        #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 '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()
        # control display of window decorations
        if self.options['fullscreen']==True:
            self.root.attributes('-fullscreen', True)
            #self.root = Tk(className="fspipresents")
            os.system('unclutter &')
        else:
            #self.root = Tk(className="pipresents")
            pass


        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']==True:
            bar=self.options['fullscreen']
            # allow just 2 pixels for the hidden taskbar - not any more
            if bar in ('left','right'):
                self.window_width=self.screen_width
            else:
                self.window_height=self.screen_height
            if bar =="left":
                self.window_x=0
            if bar =="top":
                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=self.screen_width-600
            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 start 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.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_gpio import PPIO
            # initialise the GPIO
            self.ppio=PPIO()
            # PPIO.gpio_enabled=False
            if self.ppio.init(pp_dir,self.pp_profile,self.canvas,50,self.button_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.start_shows = self.create_start_show_list()
        self.init_shows()
        self.run_shows()
        self.root.mainloop( )




# *********************
# EXIT APP
# *********************

    # kill or error
    def terminate(self,reason):
        needs_termination=False
        for start_show in self.start_shows:
            if start_show[PiPresents.SHOW]<>None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ start_show[PiPresents.NAME])
                start_show[PiPresents.SHOW].terminate(reason)
        if needs_termination==False:
            self._end(reason)



    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank']==True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
        # tidy up gpio
        if self.options['gpio']==True and self.ppio<>None:
            self.ppio.terminate()
        #tidy up time of day scheduler
        if self.tod<>None:
            self.tod.terminate()
        #close logging files 
        self.mon.finish()

        
    def on_kill_callback(self):
        self.tidy_up()
        if self.shutdown_required==True:
            call(['sudo', 'shutdown', '-h', '-t 5','now'])
        else:
            exit()

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value

# *********************
# Key and button presses
# ********************

    def shutdown_pressed(self):
        self.root.after(5000,self.on_shutdown_delay)

    def on_shutdown_delay(self):
        if self.ppio.is_pressed('shutdown'):
            self.shutdown_required=True
            self.on_break_key()

    def button_pressed(self,index,button,edge):
        self.mon.log(self, "Button Pressed: "+button)
        if button=="shutdown":
            self.shutdown_pressed()
        else:
            for start_show in self.start_shows:
                print "sending to show" , start_show[PiPresents.NAME]
                start_show[PiPresents.SHOW].button_pressed(button,edge)                 

  
    # key presses - convert from events to call to _key_pressed
    def _escape_pressed(self,event): self._key_pressed("escape")              
    def _up_pressed(self,event): self._key_pressed("up")  
    def _down_pressed(self,event): self._key_pressed("down")  
    def _return_pressed(self,event): self._key_pressed("return")
    def _pause_pressed(self,event): self._key_pressed("p")
        

    def _key_pressed(self,key_name):
        # key pressses are sent only to the controlled show.
        self.mon.log(self, "Key Pressed: "+ key_name)
        for start_show in self.start_shows:
                start_show[PiPresents.SHOW].key_pressed(key_name)          


         
    def on_break_key(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players     
        self.mon.log(self,"kill sent to shows")   
        self.terminate('killed')
 
 
    def e_on_break_key(self,event):
        self.on_break_key()

# *********************
# Start show creation and running and return from
# ********************   
        
# Extract shows from start show
    def create_start_show_list(self):
        start_shows_text=self.starter_show['start-show']
        shows=[]
        index=0
        fields= start_shows_text.split()
        for field in fields:
            show = PiPresents.SHOW_TEMPLATE        
            show[PiPresents.NAME]=field
            show[PiPresents.ID]=index 
            shows.append(copy.deepcopy(show))
            index+=1          
        return shows

    def init_shows(self):
        # build  list of shows to run by instantiating their classes.
        for start_show in self.start_shows:
            index = self.showlist.index_of_show(start_show[PiPresents.NAME])
            if index >=0:
                self.showlist.select(index)
                show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ start_show[PiPresents.NAME])
                self._end('error','show not found in showlist')
                
            if show['type']=="mediashow":
                show_obj = MediaShow(show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                start_show[PiPresents.SHOW]=show_obj


                
            elif show['type']=="menu":
                show_obj = MenuShow(show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                start_show[PiPresents.SHOW]=show_obj


            elif show['type']=="liveshow":
                show_obj= LiveShow(show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                start_show[PiPresents.SHOW]=show_obj                   
            else:
                self.mon.err(self,"unknown mediashow type in start show - "+ show['type'])
                self._end('error','unknown mediashow type')


    # run each of the shows in the list
    def run_shows(self):
        for start_show in self.start_shows:
                show_obj = start_show[PiPresents.SHOW]
                show_obj.play(start_show[PiPresents.ID],self._end_play_show,top=True,command='nil')


    def _end_play_show(self,show_id,reason,message):     
        self.mon.log(self,"Show " + str(show_id) + " returned to Pipresents with reason: " + reason )
        self.start_shows[show_id][PiPresents.SHOW]=None
        # if all the shows have ended then end Pi Presents
        all_terminated=True
        for start_show in self.start_shows:
            if start_show[PiPresents.SHOW]<>None:
                all_terminated=False
        if all_terminated==True:
            self._end(reason,message)
     
    def _end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with message: " + message)
        if reason=='error':
            self.mon.log(self, "exiting because of error")
            self.tidy_up()
            exit()            
        if reason=='killed':
            self.mon.log(self,"kill received - exiting")
            self.on_kill_callback()
        else:
            # should never be here or fatal error
            self.mon.log(self, "exiting because invalid end reasosn")
            self.tidy_up()
            exit()
class Test:
    def __init__(self, track, pp_dir, pp_home, show_params, ct):

        self.track = track
        self.show_params = show_params
        self.pp_home = pp_home
        self.ct = ct
        self.break_from_loop = False

        # create and instance of a Tkinter top level window and refer to it as 'my_window'
        my_window = Tk()
        my_window.title("AudioPlayer Test Harness")

        # change the look of the window
        my_window.configure(background='grey')
        window_width = 1500
        window_height = 900

        canvas_height = window_height
        canvas_width = window_width

        #defne response to main window closing
        my_window.protocol("WM_DELETE_WINDOW", self.terminate)

        my_window.geometry("%dx%d+200+20" % (window_width, window_height))

        # Always use CTRL-Break key to close the program as a get out of jail
        my_window.bind("<Break>", self.e_terminate)

        my_window.bind("s", self.play_event)
        my_window.bind("p", self.pause_event)
        my_window.bind("q", self.stop_event)
        my_window.bind("l", self.loop_event)
        my_window.bind("n", self.next_event)

        #setup a canvas onto which will not be drawn the video!!
        canvas = Canvas(my_window, bg='black')
        canvas.config(height=canvas_height, width=canvas_width)
        canvas.pack()
        # make sure focus is set on canvas.
        canvas.focus_set()
        self.canvas = canvas

        #create an instance of PPIO and start polling
        self.ppio = PPIO()
        self.ppio.init(pp_dir, pp_home, self.canvas, 50, self.callback)
        self.ppio.poll()

        my_window.mainloop()

    def callback(self, index, name, edge):
        print name, edge

    #key presses

    def e_terminate(self, event):
        self.terminate()

    def play_event(self, event):
        self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params,
                              self.ct)
        self.vp.play(self.track, self.on_end, self.do_ready, False,
                     self.do_starting, self.do_playing, self.do_finishing)

    # toggles pause
    def pause_event(self, event):
        self.vp.key_pressed('p')

    def stop_event(self, event):
        self.break_from_loop = True
        self.vp.key_pressed('escape')

    def loop_event(self, event):
        #just kick off the first track, callback decides what to do next
        self.break_from_loop = False
        self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params)
        self.vp.play(self.track, self.what_next, self, do_ready, False,
                     self.do_starting, self.do_playing, self.do_finishing)

    def next_event(self, event):
        self.break_from_loop = False
        self.vp.key_pressed('down')

    def what_next(self, reason, message):
        self.vp = None
        if reason in ('killed', 'error'):
            self.end(reason, message)
        else:
            if self.break_from_loop == True:
                self.break_from_loop = False
                print "test harness: loop interupted"
                return
            else:
                self.vp = AudioPlayer(self.canvas, self.show_params)
                self.vp.play(self.track, self.what_next, self.do_starting,
                             self.do_playing, self.do_finishing)

    def on_end(self, reason, message):
        self.vp = None
        print "Test Class: callback from AudioPlayer says: " + message
        if reason in ('killed', 'error'):
            self.end(reason, message)
        else:
            return

    def do_ready(self):
        print "test class message from AudioPlayer: ready to play"
        return

    def do_starting(self):
        print "test class message from AudioPlayer: do starting"
        return

    def do_playing(self):
        #self.display_time.set(self.time_string(self.vp.audio_position))
        # print "test class message from AudioPlayer: do playing"
        return

    def do_finishing(self):
        print "test class message from AudioPlayer: do ending"
        return

    def terminate(self):
        if self.vp == None:
            self.end('killled', 'killled')
        else:
            self.vp.terminate('killed')
            return

    def _end(self, reason, message):
        self.ppio.terminate()
        exit()
Beispiel #5
0
class PiPresents:
    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()

# *********************
#  RUN START SHOWS
# ********************

    def run_start_shows(self):
        #start show manager
        show_id = -1  #start show
        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 so empty show register and set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.all_shows_ended_callback)

        #parse the start shows field and start the initial shows
        start_shows_text = self.starter_show['start-show']
        self.show_manager.start_initial_shows(start_shows_text)

    #callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self, reason, message, force_shutdown):
        self.mon.log(self, "All shows ended, so terminate Pi Presents")
        if force_shutdown == True:
            self.shutdown_required = True
            self.mon.log(self, "shutdown forced by profile")
            self.terminate('killed')
        else:
            self.end(reason, message)

# *********************
# User inputs
# ********************

#gpio callback - symbol provided by gpio

    def gpio_pressed(self, index, symbol, edge):
        self.mon.log(self, "GPIO Pressed: " + symbol)
        self.input_pressed(symbol, edge, 'gpio')

    # all input events call this callback with a symbolic name.
    def input_pressed(self, symbol, edge, source):
        self.mon.log(self, "input received: " + symbol)
        if symbol == 'pp-exit':
            self.exit_pressed()
        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')
        elif symbol == 'pp-shutdownnow':
            self.shutdown_pressed('now')
        else:
            for show in self.show_manager.shows:
                show_obj = show[ShowManager.SHOW_OBJ]
                if show_obj <> None:
                    show_obj.input_pressed(symbol, edge, source)

# **************************************
# respond to exit inputs by terminating
# **************************************

    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000, self.on_shutdown_delay)
        else:
            self.shutdown_required = True
            self.exit_pressed()

    def on_shutdown_delay(self):
        if self.ppio.shutdown_pressed():
            self.shutdown_required = True
            self.exit_pressed()

    def exit_pressed(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players
        self.mon.log(self, "kill sent to shows")
        self.terminate('killed')

    # kill or error
    def terminate(self, reason):
        needs_termination = False
        for show in self.show_manager.shows:
            if show[ShowManager.SHOW_OBJ] <> None:
                needs_termination = True
                self.mon.log(
                    self,
                    "Sent terminate to show " + show[ShowManager.SHOW_REF])
                show[ShowManager.SHOW_OBJ].terminate(reason)
        if needs_termination == False:
            self.end(reason,
                     'terminate - no termination of lower levels required')

# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def end(self, reason, message):
        self.mon.log(
            self, "Pi Presents ending with message: " + reason + ' ' + message)
        if reason == 'error':
            self.tidy_up()
            self.mon.log(self, "exiting because of error")
            #close logging files
            self.mon.finish()
            exit()
        else:
            self.tidy_up()
            self.mon.log(self, "no error - exiting normally")
            #close logging files
            self.mon.finish()
            if self.shutdown_required == True:
                # call(['sudo', 'shutdown', '-h', '-t 5','now'])
                call(['sudo', 'shutdown', '-h', 'now'])
                exit()
            else:
                exit()

    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank'] == True:
            call(["xset", "s", "on"])
            call(["xset", "s", "+dpms"])

        # tidy up gpio
        if self.options['gpio'] == True and self.ppio <> None:
            self.ppio.terminate()

        #tidy up time of day scheduler
        if self.tod <> None:
            self.tod.terminate()


# *****************************
# utilitities
# ****************************

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            self.terminate("error")
        else:
            return value