" AlarmClock-wxE.py (V. Stokes) "
import wx
import time
import winsound

MINADD = 1  #For default alarm time (now + MINADD minutes)
HOURADD = 1 #For default alarm time (now + HOURADD hours)
Max_Chars_In_MESSAGE = 27

class Sound:
    def SoundAlarm(self):
        winsound.Beep(400, 200) # frequency, length
        
class StaticTextFrame(wx.Frame):
    def __init__(self, msgstr="Wake UP!"):
        wx.Frame.__init__(self, None, -1, 'Alarm message', 
                size=(400, 110))
        w, h = self.GetClientSize()
        panel = wx.Panel(self, -1)        
        font = wx.Font(20, wx.DECORATIVE, wx.ITALIC, wx.NORMAL) 
        self.SetFont(font)
        msgw, msgh = self.GetTextExtent(msgstr)
        #text = wx.StaticText(panel, -1, msgstr,((w-msgw)/2-5, (h)/2-msgh/2))           
        text = wx.StaticText(panel, -1, msgstr,((w-msgw)/2-5, (h-msgh)/2))      
        text.SetFont(font)  
        pass

class ClockWindow(wx.Window):
    def __init__(self, parent):
        wx.Window.__init__(self, parent)
       
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
        self.timer.Start(500)
        self.sound = Sound()
        
    def Draw(self, dc):
        t = time.localtime(time.time())
        st = time.strftime("%H:%M:%S", t)
        w, h = self.GetClientSize()
        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        dc.SetFont(wx.Font(30, wx.SWISS, wx.NORMAL, wx.NORMAL))
        tw, th = dc.GetTextExtent(st)
        dc.DrawText(st, (w-tw)/2, (h)/2 - th/2)
        
    def OnTimer(self, evt):
        dc = wx.BufferedDC(wx.ClientDC(self))
        self.Draw(dc)
        self.sound.SoundAlarm()        

    def OnPaint(self, evt):
        dc = wx.BufferedPaintDC(self)
        self.Draw(dc)

class TimeFrame(wx.Frame):
    def __init__(self,hourminstr):
        wx.Frame.__init__(self, None, size = (310,150),title="Wake UP! (Alarm at "+hourminstr+")")
        ClockWindow(self)        


def settime(arg):
    arg_sec = (arg.tm_hour * 60 + arg.tm_min) * 60
    now = time.localtime()
    now_sec = (now.tm_hour * 60 + now.tm_min) * 60 + now.tm_sec
    # Add 24 hours (60*60*24 = 86400 Sec) if HH:MM <= now time
    return arg_sec - now_sec + (86400 if arg_sec <= now_sec else 0)

'''
  See pp. 286-288
'''

about_txt = "Define alarm message (max of "+str(Max_Chars_In_MESSAGE)+\
          " characters) and alarm time          "


class DataXferValidator(wx.PyValidator):
    def __init__(self, data, key):
        wx.PyValidator.__init__(self)
        self.data = data
        self.key = key

    def Clone(self):
        """
        Note that every validator must implement the Clone() method.
        """
        return DataXferValidator(self.data, self.key)

    def Validate(self, win):
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()
        if self.key == 'message':
            if len(text) > Max_Chars_In_MESSAGE:
                wx.MessageBox("Message had "+str(len(text))+" characters (max allowed = "+str(Max_Chars_In_MESSAGE)+")!","Error")
                textCtrl.SetBackgroundColour("pink")
                textCtrl.SetFocus()
                textCtrl.Refresh()
                return False
                
        if self.key == 'time':
            # Check if text contains HH:MM 
            #  where 00 <= HH <= 23, 00 <= MM <= 59
            try:
                hh = int(text[0:2])
                if not (0 <= hh <= 24):
                    wx.MessageBox("hh:mm (00 <= hh <= 23)!","Error")
                    textCtrl.SetBackgroundColour("pink")
                    textCtrl.SetFocus()
                    textCtrl.Refresh()
                    return False
            except ValueError:
                wx.MessageBox("hh:mm (00 <= hh <= 23)!","Error")
                textCtrl.SetBackgroundColour("pink")
                textCtrl.SetFocus()
                textCtrl.Refresh()
                return False
               
            if not (text[2] == ':'):
                wx.MessageBox("hh:mm form required !","Error")
                textCtrl.SetBackgroundColour("pink")
                textCtrl.SetFocus()
                textCtrl.Refresh()
                return False
              
            try:
                mm = int(text[3:5])
                if not (0 <= mm <= 59):
                    wx.MessageBox("hh:mm (00 <= mm <= 59)!","Error")
                    textCtrl.SetBackgroundColour("pink")
                    textCtrl.SetFocus()
                    textCtrl.Refresh()
                    return False
            except ValueError: 
                wx.MessageBox("hh:mm (00 <= mm <= 59)!","Error")
                textCtrl.SetBackgroundColour("pink")
                textCtrl.SetFocus()
                textCtrl.Refresh()
                return False               
       
        return True

    def TransferToWindow(self):
        textCtrl = self.GetWindow()
        textCtrl.SetValue(self.data.get(self.key, ""))
        return True 

    def TransferFromWindow(self):
        textCtrl = self.GetWindow()
        self.data[self.key] = textCtrl.GetValue()
        return True

class MyDialog(wx.Dialog):
    def __init__(self, data):
        wx.Dialog.__init__(self, None, -1, "My Alarm Clock")

       # Create the text controls
        about   = wx.StaticText(self, -1, about_txt)
        msg_l  = wx.StaticText(self, -1, "Alarm message:")
        time_l = wx.StaticText(self, -1, "Alarm time:")
                  
        msg_t  = wx.TextCtrl(self, validator=DataXferValidator(data, "message"))
        time_t = wx.TextCtrl(self, validator=DataXferValidator(data, "time"))
          
       # Use standard button IDs
        okay   = wx.Button(self, wx.ID_OK)
        okay.SetDefault()
        cancel = wx.Button(self, wx.ID_CANCEL)
        
        # Layout with sizers
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(about, 0, wx.ALL, 5)
        sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
        
        fgs = wx.FlexGridSizer(3, 2, 5, 5)
        fgs.Add(msg_l, 0, wx.ALIGN_RIGHT)
        fgs.Add(msg_t, 0, wx.EXPAND)
        fgs.Add(time_l, 0, wx.ALIGN_RIGHT)
        fgs.Add(time_t, 0, wx.EXPAND)
          
        fgs.AddGrowableCol(1)
        sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)

        btns = wx.StdDialogButtonSizer()
        btns.AddButton(okay)
        btns.AddButton(cancel)
        btns.Realize()
        sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)

        self.SetSizer(sizer)
        sizer.Fit(self)
        
def SetDefaultAlarmTime():
    
    now = time.localtime()
      
    addh = 0 
    mn = now.tm_min + MINADD
    if mn > 59:
        mn = mn - 60
        addh = 1
        
    hr = now.tm_hour + addh
    if hr > 23:
        hr = 0
        
    prefix = ''
    if hr < 10:
        prefix = '0'
    hrstr = prefix+str(hr)
    prefix = ''
    if mn < 10:
        prefix = '0'
    mnstr = prefix+str(mn)
        
    timestr = hrstr+":"+mnstr  
    return timestr

if __name__ == '__main__':
    
    # Initialize wx application    
    app = wx.PySimpleApp() 
    timestr = SetDefaultAlarmTime() # "HH:MM" for alarm to go off
    
    data = { "message" : "Time for your exercise!", "time" : timestr }
    dlg = MyDialog(data)   

    retcode = dlg.ShowModal()
    dlg.Destroy()
    if retcode == wx.ID_OK:        
        
        hourminstr = data['time']
        arg = time.strptime(hourminstr, '%H:%M')
        # Set alarm time (in seconds)
        seconds = settime(arg)      
        # Now wait (countdown) for alarm (pop-up with message)
        time.sleep(seconds) # Zzzzz...z
        # Alarm time is now!
        
        # Display message window (pop-up to be displayed)
        frame1 = StaticTextFrame(data['message'])
        frame1.Show()
        
        # Display current time (digital clock) and beep until clock window closed
        frame2 = TimeFrame(hourminstr)
        frame2.Show()
        
        app.MainLoop()
    
    
    
    
    