# -*- coding: UTF-8 -*-

import sys
try:
    import wx
except:
    print('Could not load wx module.')
    sys.exit()
try:
    import wx.html
except:
    print('Could not load wx.html module.')
    sys.exit()
try:
    from wx.lib.stattext import GenStaticText
    from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
except:
    print('Could not import wx.lib.expando.')
    sys.exit()
try:
    import wx.lib.colourdb
except:
    print('Could not import wx.lib.colourdb.')
    sys.exit()

import time, shlex
from threading import Timer, Thread
from sched import scheduler
from etm.data import *
from etm.cal import *
from etm.v import version
from etm.sunmoon import *
from etm.weather import *
from etm.busyfree import *

import locale
wx.SetDefaultPyEncoding(encoding)


context_group_keys = []
keyword_group_keys = []

this_dir, this_filename = os.path.split(__file__)
from pkg_resources import resource_filename
if "wxMac" in wx.PlatformInfo:
    hourglass_type = 'png'
    hourglass = "etmlogo_128x128x32.png"
    hg_type = wx.BITMAP_TYPE_PNG
elif "wxMSW" in wx.PlatformInfo:
    hourglass_type = 'ico'
    hourglass = "etmlogo.ico"
    hg_type = wx.BITMAP_TYPE_ICO
elif "wxGTK" in wx.PlatformInfo:
    hourglass_type = 'png'
    hourglass = "etmlogo_32x32x32.png"
    hg_type = wx.BITMAP_TYPE_PNG
else:
    hourglass_type = ''
    hourglass = ''
    hg_type = ''

if hourglass:
    try:
        etm_hourglass = resource_filename(__name__, hourglass)
    except:
        etm_hourglass = hourglass

try:
    etm_alert = resource_filename(__name__, 'etm_alert.wav')
except:
    etm_alert = 'etm_alert.wav'

class MyTaskBarIcon(wx.TaskBarIcon):
    TBMENU_SHOW  = wx.NewId()
    TBMENU_HIDE  = wx.NewId()
    TBMENU_CLOSE = wx.NewId()

    def __init__(self, frame):
        wx.TaskBarIcon.__init__(self)
        self.frame = frame
        if hourglass_type == 'ico':
            self.SetIcon(wx.Icon(etm_hourglass, wx.BITMAP_TYPE_ICO), 'etm')
        elif hourglass_type == 'png':
            self.SetIcon(wx.Icon(etm_hourglass, wx.BITMAP_TYPE_PNG), 'etm')

        self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
        self.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=self.TBMENU_SHOW)
        self.Bind(wx.EVT_MENU, self.OnTaskBarDeActivate, id=self.TBMENU_HIDE)
        self.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)
        
    def CreatePopupMenu(self):
        menu = wx.Menu()
        menu.Append(self.TBMENU_SHOW, "Show")
        menu.Append(self.TBMENU_HIDE, "Hide")
        menu.AppendSeparator()
        menu.Append(self.TBMENU_CLOSE, "Close")
        return menu
    
    def OnTaskBarClose(self, event):
        self.frame.Close()
    
    def OnTaskBarActivate(self, event):
        if "wxMSW" in wx.PlatformInfo and self.frame.IsIconized():
            self.frame.Iconize(False)
        if not self.frame.IsShown():
            self.frame.Show(True)
        self.frame.Raise()
        
    def OnTaskBarDeActivate(self, event):
        if self.frame.IsShown():
            self.frame.Hide()


class MyAlert(wx.Frame):
    def __init__(self, title, message):
        wx.Frame.__init__(self, None, -1, title, size=(-1, 120),
            style=wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP)
        panel = wx.Panel(self, -1)
        self.panel = panel
        self.txt = wx.StaticText(panel, -1, message)
        if hourglass_type == 'ico':
            img = wx.Image(etm_hourglass, wx.BITMAP_TYPE_ICO).Scale(48,48)
        elif hourglass_type == 'png':
            img = wx.Image(etm_hourglass, wx.BITMAP_TYPE_PNG).Scale(48,48)
        bit = img.ConvertToBitmap()
        self.img = wx.StaticBitmap(panel, -1, bit)
        box = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(self.img, 0, wx.LEFT | wx.RIGHT, 5)
        hbox.Add(self.txt, 1, wx.EXPAND | wx.ALL, 10)
        box.Add(hbox, 1)
        subbox = wx.BoxSizer(wx.HORIZONTAL)
        btn = wx.Button(panel, wx.ID_OK, "OK")
        btn.SetDefault()
        subbox.Add(btn, 0, wx.ALIGN_RIGHT | wx.ALL, 8)
        self.Bind(wx.EVT_BUTTON, self.OnClose, btn)
        box.Add(subbox, 0, wx.ALIGN_RIGHT | wx.ALL, 0)
        panel.SetSizer(box)
        self.Layout()
        sound = wx.Sound(etm_alert)
        sound.Play(wx.SOUND_SYNC)
        # wx.Bell()
    
    def OnCancel(self, event):
        self.Destroy()
    
    def OnClose(self, event):
        self.Destroy()

class MyHtmlWindow(wx.html.HtmlWindow):
    def __init__(self, parent, id, pos = wx.DefaultPosition, size=wx.DefaultSize, style=0):
        wx.html.HtmlWindow.__init__(self, parent, id, pos, size,
            style=wx.BORDER_SUNKEN)
        if "gtk2" in wx.PlatformInfo:
            self.SetStandardFonts(htmlfont+4, '', '')
        else:
            self.SetFonts('', '', [i for i in range(htmlfont,
                htmlfont+13,2)])
    
    # Keep it from taking the focus
    def AcceptsFocus(self, *args, **kwargs):
        return False

class MyStaticText(GenStaticText):
    # Keep it from taking the focus
    def AcceptsFocus(self, *args, **kwargs):
        return False

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
        self.SetDimensions(xpos,ypos, wxwidth, wxheight)
        self.SetMinSize((wxwidth, wxheight))
        if hourglass_type == 'ico':
            self.SetIcon(wx.Icon(etm_hourglass, wx.BITMAP_TYPE_ICO))
            self.tbicon = MyTaskBarIcon(self)
        elif hourglass_type == 'png':
            self.SetIcon(wx.Icon(etm_hourglass, wx.BITMAP_TYPE_PNG))
            self.tbicon = MyTaskBarIcon(self)
        panel = wx.Panel(self, -1)
        self.panel = panel
        self.panel.SetBackgroundColour(detail_bgcolor)
        self.leader = False
        self.alert_count = 0
        self.alert_ids = []
        self.alert_msg = []
        self.alert_times = []
        self.alert_update = None
        self.alerts_running = False
        self.auto_reload = False
        self.prompt = "etm %s   F1: toggle help    (%s)" % (version,
            self.alert_count)
        self.entrybartext = ""
        self.verbose_toggle = verbose
        self.title = ""
        self.html = ""
        self.cur_detail = ""
        self.header = None
        self.clip = None
        self.footer = None
        self.minattr = None
        self.advance = 0
        self.cal_advance = 0
        self.yesterday = False # for testing
        self.shiftopts = {}
        self.new_today = False
        self.in_getresponse = False
        self.show_calendar = False
        self.show_help = False
        self.history = {}
        self.filehash = {}
        self.linehash = {}
        self.options = {}
        self.function = ''
        self.orig_entry = ''
        self.currentpage = ''
        self.file = ''
        self.cmd = 'a'
        self.last_cmd = 'a'
        self.entry_args = []
        self.args = []
        self.key = None
        self.prnt= ""
        self.field_entry = 0 # for tab completion
        self.view_memo = {}
        self.prnt_memo = {}
        self.clip_memo = {}
        self.begend_memo = {}
        self.timer_running = False
        self.timer_paused = False
        self.history['agenda'] = agenda_history
        self.history['list'] = list_history
        self.history['busy'] = busy_history
        self.history['reckoning'] = reckoning_history
        self.display = 'agenda'
        self.show_hist = False
        self.timer_active = False
        tfont = wx.Font(basefontsize, wx.DEFAULT,
                wx.ITALIC, wx.BOLD)
        efont = wx.Font(basefontsize, wx.DEFAULT,
                wx.NORMAL, wx.NORMAL)
        dfont = wx.Font(basefontsize, wx.DEFAULT,
                wx.ITALIC, wx.NORMAL)
        self.Bind(wx.EVT_CLOSE, self.OnQuit)
        self.Bind(wx.EVT_ICONIZE, self.OnIconify)
        # the details bar
        self.detail_bar = MyStaticText(panel, -1, " ", size = (-1,-1),
                style = wx.BORDER_NONE | wx.ST_NO_AUTORESIZE)
        self.detail_bar.SetFont(dfont)
        self.detail_bar.SetForegroundColour(detail_fgcolor)
        self.detail_bar.SetBackgroundColour(detail_bgcolor)
        self.detail_bar_id = self.detail_bar.GetId()
        # the entry bar and key binding
        self.entry_bar = ExpandoTextCtrl(panel, size=(-1,-1),
            value="This control will expand as you type",
            style = wx.BORDER_SUNKEN )
        self.entry_bar.SetBackgroundColour('%s' % main_bgcolor)
        self.entry_bar.SetForegroundColour('%s' % main_fgcolor)
        self.entry_bar.SetFont(efont)
        self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.entry_bar)
        self.entry_bar.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
        self.entry_bar.Bind(wx.EVT_CHAR, self.OnEntryBarChar)
        # the html window
        self.htmlwin = MyHtmlWindow(panel, -1)
        self.htmlwin.Bind(wx.EVT_CHAR, self.OnChar)
        self.htmlwin.SetRelatedFrame(self, "%s")
        self.htmlwin.SetBorders(5)
        self.printer = wx.html.HtmlEasyPrinting()
        self.printer.SetFonts('', '', [i for i in range(htmlprintfont,
            htmlprintfont+13, 2)])
        self.printdata = self.printer.GetPrintData()
        self.printdata.SetColour(False)
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(self.htmlwin, 1, wx.EXPAND | wx.ALL, 2)
        vbox.Add(self.detail_bar, 0, wx.EXPAND )
        vbox.Add(self.entry_bar, 0, wx.EXPAND | wx.ALL, 2)
        panel.SetSizer(vbox)
        self.Layout()
        self.setprompt(True)
        self.data = ETMData()
        msg = self.data.loadData()
        info = self.data.process_items()
        self.lastmtime = self.data.getmtime()
        if msg:
            self.show_errors(msg)
        elif info:
            self.setmessage(info)
        self.show_view(self.cmd, self.args)
        self.start_alerts()
        self.Show(True)
        self.Refresh()

    def OnIconify(self, event):
        if "wxMSW" in wx.PlatformInfo:
            self.Hide()
   
    def OnKillFocus(self, event):
        self.entry_bar.SetFocus()
    
    def getPos(self):
        self.x_pos, self.y_pos = self.htmlwin.GetViewStart()
    
    def restorePos(self):
        self.htmlwin.Scroll(self.x_pos, self.y_pos)
    
    def show_view(self, cmd, args):
        """Display the view for cmd using args and the value of
        self.verbose_toggle, caching the result. If called again with
        the same cmd, args and verbose_ toggle, the cache value will be
        displayed."""
        self.cmd = cmd
        self.args = args
        today = get_today()
        if self.data.today != today or self.new_today:
            self.data.today = today
            self.view_memo.clear()
            self.begend_memo.clear()
            self.prnt_memo.clear()
            self.new_today = True
            self.data.process_items()
        if self.data.changed:
            self.data.today = today
            self.view_memo.clear()
            self.begend_memo.clear()
            self.prnt_memo.clear()            
            self.data.loadData()
            if self.data.load_errors:
                self.show_errors(self.data.load_errors)
            self.data.process_items()
            if self.auto_reload:
                self.auto_reload = False
                self.ShowAlert('etm', 'automatically reloaded data files',
                 4)
            self.lastmtime = self.data.getmtime()
        key = (self.cmd, tuple(self.args), self.data.today,
                self.verbose_toggle, self.data.show_idnums, self.advance)
        self.key = key
        if key not in self.view_memo:
            (options, err) = parse_args(self.cmd, self.args)
            if self.cmd != 'a' and self.advance:
                options['begin'] = options['begin'] + \
                        self.advance*(options['days'])*oneday
                options['end'] = options['end'] + \
                        self.advance*(options['days'])*oneday
            if self.verbose_toggle:
                minattr = -2
            else:
                minattr = -1
            h, z, l, f = self.data.prepare_view(options)
            self.header = h
            self.clip_memo[key] = l
            self.footer = f
            self.minattr = minattr
            lst = [ "<title>%s</title>".encode(encoding) % h.str() ]
            prnt = [ "<title>%s</title>".encode(encoding) % h.str() ]
            prnt.append('<body text="%s">'.encode(encoding) %
                (print_fgcolor))
            lst.append('<body text="%s" bgcolor="%s">'.encode(encoding) 
                % (main_fgcolor,main_bgcolor))
            if self.cmd in ['a', 'l']:
                # table output
                lst.append('<table width="100%" border="0" cellpadding="0"'
                        ' cellspacing="2" columns="4">'.encode(encoding))
                prnt.append('<table width="100%" border="0" cellpadding="0"'
                        ' cellspacing="2" columns="4">'.encode(encoding))
                for i in l:
                    lst.append(i.htm(minattr).encode(encoding))
                    prnt.append(i.htm(minattr,True).encode(encoding))
                lst.append('</table>'.encode(encoding))
                prnt.append('</table>'.encode(encoding))
            else:
                # preformated output
                lst.append('<pre>')
                prnt.append('<pre>')
                for i in l:
                    lst.append(i.pre(minattr))
                    prnt.append(i.pre(minattr, True))
                lst.append('</pre>'.encode(encoding))
                prnt.append('</pre>'.encode(encoding))
            lst.append('</body>'.encode(encoding))
            prnt.append('</body>'.encode(encoding))
            self.view_memo[key] = ("\n".encode(encoding)).join(lst)
            self.prnt_memo[key] = ("\n".encode(encoding)).join(prnt)
            self.begend_memo[key] = [options['begin'], options['end']]
        if self.cmd in ['a', 'l']:
            if self.verbose_toggle:
                self.prompt = "etm %s    F1: toggle help   v: hide details" % (version)
            else:
                self.prompt = "etm %s    F1: toggle help   v: show details" % (version)

        else:
            self.prompt = "etm %s    F1: toggle help " % (version)
        if self.cmd in ['l', 'b', 'r'] or self.show_calendar:
            self.prompt =   "%s    L/R arrows: shift interval" \
                % self.prompt
        elif self.cmd == 'a' or self.show_calendar:
            self.prompt =   "%s    " \
                % self.prompt
        self.html = self.view_memo[key]
        self.prnt = self.prnt_memo[key]
        self.clip = self.clip_memo[key]
        self.currentpage = self.html
        self.htmlwin.SetPage(self.html)
        self.setprompt(True)
        self.Refresh()
    
    def view2Clipboard(self):
        lst = [ self.header.str() ]
        for i in self.clip:
            lst.append(i.str(self.minattr))
        text = "\n".join(lst)
        self.do = wx.TextDataObject()
        self.do.SetText(text)
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(self.do)
            wx.TheClipboard.Close()
            self.setmessage('Saved current view to system clipboard.', True)
        else:
            self.show_errors("Unable to open the clipboard")
    
    def OnRefit(self, evt):
        # For the Expando control
        self.Fit()
    
    def setpage(self, html):
        # save title and html
        self.html = html
        self.currentpage = html
        self.showoutput(html)
        
    def showtable(self, h,l):
        l.insert(0, "<title>%s</title>" % h)
        l.insert(1, '<table width="100%" border="0" cellpadding="0" cellspacing="2" columns="4">')
        l.append("</table>")
        html = "\n".join(l)
        self.currentpage = html
        self.setpage(html)
    
    def showoutput(self, html):
        self.setmessage('<Esc> returns to %s display ' % self.display)
        self.currentpage = html
        self.htmlwin.SetPage(html)
        
    def show_history(self):
        histlst = self.history[self.function]
        if len(histlst) > 0:
            s = """\
The current %s view option string history.
%s option strings used in this session are  
temporarily stored here and permanent entries can  
be added to '%s_history' in your etmrc.
""" % (self.function, self.function, self.function)
            self.EntryBarInsert(self.getSelection(s, histlst))
        else:
            s = """\
The %s view option string history is currently empty. 
%s option strings used in this session will be 
stored there temporarily and permanent entries can  
be added to '%s_history' in your etmrc.      
""" % (self.function, self.function, self.function)
            dlg = wx.MessageDialog(self, s,
                'etm %s view' % self.function,
                wx.OK
                )
            res = dlg.ShowModal()
            dlg.Destroy()

    def show_templates(self):
        if len(templates) > 0:
            s = """\
The current list of 'templates' in your etmrc.""" 
            self.EntryBarInsert(self.getSelection(s, templates))
        else:
            s = """\
The list of 'templates' in your etmrc is currently empty."""
            dlg = wx.MessageDialog(self, s,
                'etm',
                wx.OK
                )
            res = dlg.ShowModal()
            dlg.Destroy()

    
    def setprompt(self, force=False):
        if force or self.timer_active:
            self.cur_detail = "  %s" % self.prompt
            if self.alert_count > 0:
                self.detail_bar.SetLabel(
                    "%s%snext alert: %s" %
                    (self.cur_detail, ' '*4, self.alert_times[0]))
            else:
                self.detail_bar.SetLabel("%s" % self.cur_detail)
            self.entry_bar.SetValue('')
            self.entry_bar.Enable(False)
            self.entry_bar.SetBackgroundColour('%s' % main_bgcolor)
            self.entry_bar.SetForegroundColour('%s' % main_fgcolor)
            self.htmlwin.SetFocus()
            self.timer_active = False
    
    def refreshprompt(self):
        if self.alert_count > 0:
            self.detail_bar.SetLabel(
            "%s%snext alert: %s" % (self.cur_detail, ' '*4,
             self.alert_times[0]))
        else:
            self.detail_bar.SetLabel("%s" % self.cur_detail)
    
    def maybereload(self):
        l = self.data.getmtime()
        self.auto_reload = False
        self.new_today = False
        if l > self.lastmtime:
            self.data.changed = True
            self.auto_reload = True
        if self.data.today != get_today():
            self.new_today = True
        if not self.in_getresponse and (self.auto_reload or
         self.data.changed or self.new_today):
            wx.CallAfter(self.show_view, self.cmd, self.args)
    
    def setmessage(self, message, pause=False):
        fmtmsg = "  %s" % message
        self.entry_bar.SetValue('')
        self.entry_bar.Enable(False)
        self.entry_bar.SetBackgroundColour('%s' % main_bgcolor)
        self.entry_bar.SetForegroundColour('%s' % main_fgcolor)
        if pause:
            self.detail_bar.SetLabel("%s" % fmtmsg)
            self.timer_active = True
            timer = Timer(delay, self.setprompt)
            timer.start()
        else:
            self.cur_detail = fmtmsg
            if self.alert_count > 0:
                self.detail_bar.SetLabel(
                "%s%snext alert: %s" %
                    (fmtmsg, ' '*4, self.alert_times[0]))
            else:
                self.detail_bar.SetLabel("%s" % self.cur_detail)
    
    def start_alerts(self):
        self.alerts_running = True
        self.alert_sched = scheduler(time.time, time.sleep)
        self.alert_thread = Thread(target=self.alert_sched.run)
        self.alert_sched.enter(0, 0, self.update_alerts, ())
        self.alert_thread.setDaemon(1)
        self.alert_thread.start()
    
    def stop_alerts(self):
        if self.alerts_running:
            self.alerts_running = False
            if not self.alert_sched.empty():
                for a in self.alert_ids:
                    try:
                        self.alert_sched.cancel(a)
                    except:
                        pass
        return(True)
    
    def alert_status(self):
        ret = ['<title>alert status</title>',
            '<body text="%s" bgcolor="%s">',
                '<pre>']
        if self.alerts_running:
            curtime = time.time()
            lastmtime = self.alert_update
            nextsec = int(lastmtime + 60)
            nexttime = datetime.datetime.fromtimestamp(nextsec).strftime("%H:%M:%S")
            difftime = int(lastmtime + inc - curtime)
            if difftime >= 0:
                lines = self.alert_msg
                if len(lines) > 0:
                    ret.extend(["The following alerts are currently in the queue:", ''])
                    for line in lines:
                        ret.append('    <font color="magenta">%s</font>' % line)
                else:
                    ret.append("The alert queue is currently empty.")
                ret.extend(['',
                    "The next update will be %s seconds from now at %s." %
                    (difftime, nexttime)])
        else:
            ret.append("The alert queue is not running.")
        # ret.append("getresponse active: %s" % self.in_getresponse )
        ret.append("</pre></body>")
        html = "\n".join(ret)
        return(html)
    
    def update_queue(self):
        self.alert_msg = []
        self.alert_times = []
        self.alert_update = time.time()
        queue = self.alert_sched.queue
        self.alert_count = len(self.alert_sched.queue) - 1
        for e in queue:
            m = " ".join(e[3]).strip()
            dt = datetime.datetime.fromtimestamp(e[0])
            tfmt = dt.strftime(timefmt)
            if m:
                self.alert_msg.append("%s: %s" % (tfmt, m))
                if use_ampm:
                    t = leadingzero.sub('', tfmt)
                    t = t[:-1].lower()
                else:
                    t = tfmt
                self.alert_times.append(t)
    
    def update_alerts(self):
        # try:
        self.alert_sched.enter(60, 0, self.update_alerts, ())
        self.maybereload()
        self.clear_alerts()
        self.load_alerts()
        self.update_queue()
        if not self.in_getresponse:
            wx.CallAfter(self.refreshprompt)
        # except:
        #     pass
    
    def clear_alerts(self):
        if not self.alert_sched.empty():
            for a in self.alert_ids:
                try:
                    self.alert_sched.cancel(a)
                except:
                    pass
    
    def load_alerts(self):
        self.alert_ids = []
        # self.data.alerts is a list of tuples: (title, starttime, minutes)
        # where minutes is a list of integer minutes before
        # starttime at which alerts should be given
        for event in self.data.alerts:
            (title, starttime, alerts) = event
            n = parse(starttime)
            now = datetime.datetime.now()
            for alert in alerts:
                t = n - alert * oneminute
                if t < now:
                    continue
                if alert > 1:
                    msg = "%s %s %s" % (title, alert, minutes)
                elif alert == 1:
                    msg = "%s 1 %s" % (title, minute)
                else:
                    msg = "%s %s" % (title, rightnow)
                at = time.mktime(t.timetuple())
                id = self.alert_sched.enterabs(at, 1, self.onAlert, (msg,))
                self.alert_ids.append(id)
    
    def ShowAlert(self, T, msg, s=seconds):
        self.alert = MyAlert(T, msg)
        self.alert.Show()
        if s > 0:
            self.timer = wx.CallLater(s*1000, self.alert.Destroy)
    
    def onAlert(self, msg):
        wx.CallAfter(self.update_queue)
        wx.CallAfter(self.refreshprompt)
        T = time.strftime(timefmt, time.localtime(time.time()))
        if use_ampm:
            T = leadingzero.sub('', T)
        t = "%s %s" % (thetimeis, T)
        rephash = {'t' : t, 'T' : T, 'm' : msg}
        if alertcmd:
            cmd = alertcmd % rephash
            os.system(cmd.encode(encoding))
        else:
            wx.CallAfter(self.ShowAlert, T, msg)
    
    def getresponse(self, prompt, default="", title="", html=""):
        self.in_getresponse = True
        self.entrybartext = ""
        if html != "":
            self.showoutput(html)
        self.timer_active = False
        self.detail_bar.SetLabel("  %s" % prompt)
        # reverse colors when active
        self.entry_bar.SetBackgroundColour('%s' % entry_bgcolor)
        self.entry_bar.SetForegroundColour('%s' % entry_fgcolor)
        self.entry_bar.Enable(True)
        self.entry_bar.SetFocus()
        self.entry_bar.WriteText(default)
    
    def OnPrint(self, event):
        self.printer.SetHeader(
          '<center><font size="+1">%s</font></center>' %
          self.htmlwin.GetOpenedPageTitle())
        self.printer.SetFooter(
                '<center>Page @PAGENUM@ of @PAGESCNT@</center>')
        self.printer.PrintText(self.prnt)
    
    def OnQuit(self, evnt=None):
        self.timer_active = False
        self.stop_alerts()
        self.tbicon.Destroy()
        self.Destroy()
    
    def cancel(self, msg=[]):
        if msg:
            ok = self.confirm(msg)
            if ok != wx.ID_YES:
                return()
        self.in_getresponse = False
        self.cmd = self.last_cmd
        self.getPos()
        self.show_view(self.cmd, self.args)
        self.restorePos()
        if not msg:
            self.setmessage('cancelled', True)
    
    def EntryBarAppend(self, str):
        cur_pos = self.entry_bar.GetInsertionPoint()
        self.entry_bar.AppendText(str)
        self.entry_bar.SetInsertionPoint(cur_pos)
    
    def EntryBarInsert(self, str):
        self.entry_bar.WriteText(str)
    
    def EntryBarEnter(self, event):
        self.entrybartext = self.entry_bar.GetValue().strip()
        if self.entrybartext or self.cmd in ['A', 'L', 'B', 'R']:
            self.in_getresponse = False
            return self.functionHash[self.function](self)
        elif confirm_cancel_entry:
            self.cancel(['Cancel entry?'])
        else:
            self.cancel()

    def getDate(self):
        dialog = ETMCal()
        result = dialog.ShowModal()
        if result == wx.ID_OK and dialog.selecteddate:
            retval = dialog.selecteddate
        else:
            retval = ''
        dialog.Destroy()
        return retval
    
    def getSelection(self, prompt, choices, dflt=None):
        dlg = wx.SingleChoiceDialog(self, prompt, 'etm',
                choices, wx.CHOICEDLG_STYLE)
        if dflt:
            dlg.SetSelection(dflt)
        if dlg.ShowModal() == wx.ID_OK:
            return dlg.GetStringSelection()
        else:
            return ""
    
    def getFile(self, type, mode='append'):
        file = None
        if mode == 'create':
            filedlg = wx.FileDialog(
                self, message = "New project file",
                defaultDir = etmdata,
                defaultFile="",
                wildcard="etm project file (*.txt)|*.txt",
                style=wx.SAVE
            )
            if filedlg.ShowModal() == wx.ID_OK:
                file = filedlg.GetPath()
                name, ext = os.path.splitext(file)
                fname = "%s.txt" % name
                if os.path.isfile(fname):
                    self.show_errors(
                            ["Error: file '%s' already exists" % fname])
                    return()
                return(fname)
            else:
                return()
        elif mode == 'open':
            filedlg = wx.FileDialog(
                self, message = "Open project file in external editor",
                defaultDir = etmdata,
                defaultFile="",
                wildcard="etm project file (*.txt)|*.txt",
                style=wx.OPEN
            )
            if filedlg.ShowModal() == wx.ID_OK:
                file = filedlg.GetPath()
                return(file)
            else:
                return()
        else:
            if type == 'event':
                cur = self.data.current_hash[event]
            elif type == 'task':
                cur = self.data.current_hash[task]
            elif type == 'action':
                cur = self.data.current_hash[action]
            elif type == 'note':
                cur = self.data.current_hash[note]
            flst = self.data.projects.keys()
            flst.sort()
            dflt = flst.index(cur)
            if mode == 'append':
                file = self.getSelection("The project file for the new %s" %
                        type, flst, dflt)
            elif mode == 'open':
                file = self.getSelection("The project file to open with %s" %
                        editor, flst, dflt)
        if file:
            return(os.path.join(etmdata, file))
        else:
            return()

    def show_errors(self, msg):
        s = "\n".join(msg)
        dlg = wx.MessageDialog(self, s,
            'etm error',
            wx.OK
            )
        res = dlg.ShowModal()
        dlg.Destroy()
        return(res)

    def confirm(self, msg, prompt="etm confirmation"):
        s = "\n".join(msg)
        # dlg = MyDialog(self.entry_bar, prompt, s)
        dlg = wx.MessageDialog(self.entry_bar, s, prompt,
                wx.YES_NO | wx.YES_DEFAULT )
        res = dlg.ShowModal()
        dlg.Destroy()
        return(res)

    
    def show_pre(self, h, l, setpage=False):
        html = """
        <title>%s</title>
        <pre>%s</pre>
        """ % (h, '\n'.join(l))
        if setpage:
            self.setpage(html)
        else:
            self.showoutput(html)
            
    def getEntryBarAsList(self):
        arglst = shlex.split(self.entrybartext.encode(encoding))
        return(arglst)
    
    def show_agenda(self):
        self.display = 'agenda'
        self.advance = 0
        if self.cmd == 'A' and self.entrybartext:
            if self.entrybartext not in self.history['agenda']:
                self.history['agenda'].insert(0, self.entrybartext)
            arglst = self.getEntryBarAsList()
        else:
            arglst = []
        self.show_view('a', arglst)
    
    def show_list(self):
        self.display = 'list'
        # self.advance = 0
        if self.cmd == 'L' and self.entrybartext:
            if self.entrybartext not in self.history['list']:
                self.history['list'].insert(0, self.entrybartext)
            arglst = self.getEntryBarAsList()
        else:
            arglst = []
        self.cmd = 'l'
        self.show_view('l', arglst)
    
    def show_busy(self):
        self.display = 'busy'
        # self.advance = 0
        if self.cmd == 'B' and self.entrybartext:
            if self.entrybartext not in self.history['busy']:
                self.history['busy'].insert(0, self.entrybartext)
            arglst = self.getEntryBarAsList()
        else:
            arglst = []
        self.cmd = 'b'
        self.show_view('b', arglst)
    
    def show_reckoning(self):
        self.display = 'reckoning'
        # self.advance = 0
        if self.cmd == 'R':
            if self.entrybartext not in self.history['reckoning']:
                self.history['reckoning'].insert(0, self.entrybartext)
            arglst = self.getEntryBarAsList()
        else:
            arglst = []
        self.cmd = 'r'
        self.show_view('r', arglst)
    
    def edit_entry(self):
        num = str(self.entrybartext)
        if not self.check_idnum(num):
            return()
        msg = []
        chrcode = ''
        f, n = self.data.idnum_hash[num]
        line = self.data.getlinefromfile(n, f)
        self.orig_entry = line
        m = item_regex.match(line)
        if m:
            chrcode = m.group(1)[0]
            if chrcode in ['+', '-', '.', '_']:
                entry_type = 't'
            else: # *, ~
                if chrcode == '*':
                    entry_type = 'e'
                elif chrcode == '~':
                    entry_type = 'a'
                elif chrcode == '!':
                    entry_type = 'n'
        else:
            msg.append("    %s does not begin with a character in [+-~.*_*]" % line)
        self.function = 'process_entry'
        self.entry_args = (entry_type, f, n, 'r', True)
        self.getresponse('options for modified %s: ' %
            self.typeHash[entry_type][0],
                line,
                "modify %s" % self.typeHash[entry_type][0],
                "\n".join(self.typeHash[entry_type][1]))
    
    def edit_item(self):
        num = str(self.entrybartext)
        if not self.check_idnum(num):
            return()
        msg = []
        chrcode = ''
        f, n = self.data.idnum_hash[num]
        command = editold % {'e': editor, 'n': n, 'f': f}
        os.system(command)
        self.data.changed = True
        self.show_view(self.cmd, self.args)
        self.setmessage('finished editing %s' % self.file, True)
    
    def copy_entry(self):
        num = str(self.entrybartext)
        if not self.check_idnum(num):
            return()
        msg = []
        chrcode = ''
        f, n = self.data.idnum_hash[num]
        text = self.data.getlinefromfile(n, f)
        tdo = wx.TextDataObject()
        tdo.SetText(text)
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(tdo)
            wx.TheClipboard.Close()
            self.setmessage('Saved item to system clipboard.', True)
        else:
            self.show_errors("Unable to open the clipboard")

    def new_entry(self):
        global timer_data
        entry_type = self.entry_type
        f = self.file
        self.function = 'process_entry'
        self.entry_args = (entry_type, f, 0, 'a', True)
        self.process_entry()
    
    def new_project(self):
        f = self.file
        self.function = 'process_entry'
        self.entry_args = ('j', f, 0, 'c', False)
        self.process_entry()
    
    def open_project(self):
        command = editold % {'e': editor, 'n': 1, 'f': self.file}
        os.system(command)
        self.data.changed = True
        self.show_view(self.cmd, self.args)
        self.setmessage('finished editing %s' % self.file, True)
    
    def mark_finished(self):
        res = str(self.entrybartext).split()
        num = res[0]
        if self.check_idnum(num):
            f, n = self.data.idnum_hash[num]
        else:
            return()
        line = self.data.getlinefromfile(n, f)
        if len(res) > 1:
            date = parse_date(" ".join(res[1:]))
            datefmt = date.strftime(date_fmt)
        else:
            date = None
            datefmt = get_today().strftime(date_fmt)
        msg = [
        '  Do you really want to mark the following task finished',
        '    on %s:' % datefmt,
        '',
        '    %s' % line
        ]
        if confirm_finish and self.confirm(msg) != wx.ID_YES:
            self.cancel()
        else:
            (ok, msg) = self.data.mark_complete(num, date)
            self.show_view(self.cmd, self.args)
            if ok:
                self.setmessage(msg, True)
            else:
                self.show_errors([msg])
    
    def mark_unfinished(self):
        res = str(self.entrybartext).split()
        num = res[0]
        if self.check_idnum(num):
            f, n = self.data.idnum_hash[num]
        else:
            return()
        line = self.data.getlinefromfile(n, f)
        msg = [
        '  Do you really want to mark the following task un-finished:',
        '',
        '    %s' % line
        ]
        if confirm_finish and self.confirm(msg) != wx.ID_YES:
            self.cancel()
        else:
            (ok, msg) = self.data.mark_incomplete(num)
            self.show_view(self.cmd, self.args)
            if ok:
                self.setmessage(msg, True)
            else:
                self.show_errors([msg])
    
    def check_idnum(self, idnum):
        if has(self.data.idnum_hash, idnum):
            return(True)
        else:
            self.show_errors(
                ["Could not find an item corresponding to idnum '%s'" 
                    % idnum])
            return(False)
    
    def delete_item(self):
        idnum = str(self.entrybartext)
        if self.check_idnum(idnum):
            f, n = self.data.idnum_hash[idnum]
        else:
            return()
        line = self.data.getlinefromfile(n, f)
        msg = [
        '  Do you really want to permanently delete the entry:',
        '',
        '    %s' % line
        ]
        if confirm_delete and self.confirm(msg) != wx.ID_YES:
            self.cancel()
        else:
            self.data.deletelineinfile(n, f)
            self.show_view(self.cmd, self.args)
            self.setmessage('created backup and deleted entry', True)
    
    def start_timer(self):
        #  global msg, timer_data, timer_running
        self.timer_running = True
        self.timer_paused = False
        self.timer_entry = self.entrybartext
        sdt = datetime.datetime.now()
        d = sdt.strftime(date_fmt)
        t = sdt.strftime(timefmt)
        if use_ampm:
            t = t.lower()[:-1]
        sec = 0
        self.timer_data = [sdt, sec]
        self.setmessage("Timer for '%s' started at %s. 'i' interrupts; 'I' stops."
                % (self.timer_entry, t), True)
    
    def interrupt_timer(self):
        #  global msg, timer_data, timer_running
        self.timer_paused = True
        sdt, sec = self.timer_data
        edt = datetime.datetime.now()
        sec += (edt - sdt).seconds
        minutes = sec/60
        seconds = sec%60
        t = edt.strftime(timefmt)
        if use_ampm:
            t = t.lower()[:-1]
        self.timer_data = [edt, sec]
        self.setmessage("Timer for '%s' paused at %s; %sm %ss. 'i' restarts; 'I' stops." % (self.timer_entry, t, minutes, seconds), True)
    
    def restart_timer(self):
        #  global msg, timer_data, timer_running
        self.timer_paused = False
        edt, sec = self.timer_data
        sdt = datetime.datetime.now()
        minutes = sec/60
        seconds = sec%60
        d = sdt.strftime(date_fmt)
        t = sdt.strftime(timefmt)
        if use_ampm:
            t = t.lower()[:-1]
        self.timer_data = [sdt, sec]
        self.setmessage("Timer for %s restarted at %s; %sm %ss. 'i' interrupts; 'I' stops."
                % (self.timer_entry, t, minutes, seconds), True)
    
    def stop_timer(self):
        #  global msg, timer_data, timer_running
        self.timer_running = False
        self.timer_paused = False
        sdt, sec = self.timer_data
        edt = datetime.datetime.now()
        d = edt.strftime(date_fmt)
        sec += (edt - sdt).seconds
        minutes = sec/60
        seconds = sec%60
        p = minutes
        if seconds > 0:
            p += 1
        t = edt.strftime(timefmt)
        if use_ampm:
            t = t.lower()[:-1]
        self.setmessage("Timer for '%s' stopped at %s; %sm %ss." %
                (self.timer_entry, t, minutes, seconds), True)
        return(d, p)
    
    def process_entry(self):
        entry_type, file, linenum, mode, item = self.entry_args
        s = self.entrybartext
        s_orig = self.orig_entry
        if s == s_orig:
            self.cancel()
            return()
        # mode = a)ppend, r)eplace
        prefix = None
        m0 = []
        s_orig = s
        msg = []
        writeentry = False
        s = endline_regex.sub('', s)
        title = ''
        sl = []
        m1, hsh = self.data.line2hash({},s, item)
        m2, tsk = self.data.check_hash(hsh, item)
        m3 = []
        m4 = []
        # get the possibly changed chrcode
        if 'chrcode' in tsk:
            if tsk['chrcode'] == '':
                new_type = 'j'
            elif tsk['chrcode'][0] == '*':
                new_type = 'e'
            elif tsk['chrcode'][0] in ['+', '-', '.', '_']:
                new_type = 't'
            elif tsk['chrcode'][0] == '~':
                new_type = 'a'
            elif tsk['chrcode'][0] == '!':
                new_type = 'n'
        else:
            new_type = 'unknown'
        if new_type != entry_type:
            # chrcode has changed
            if new_type in ['e', 't', 'a', 'n'] and \
                entry_type in ['e', 't', 'a', 'n']:
                # neither is a project, ok to change
                m4.append("Warning: changing entry type from '%s' to '%s'."
                    % (entry_type, new_type))
                entry_type = new_type
            else:
                # at least one must be 'j'
                m3.append("Error: it is not possible to change the entry type from '%s' to '%s'." % (entry_type, new_type))
        if entry_type == 'e':
            type = 'event'
            cur = self.data.current_hash[event]
            keys = event_keys
            prefix = '* '
        elif entry_type == 't':
            type = 'task'
            keys = task_keys
            cur = self.data.current_hash[task]
            prefix = '. '
        elif entry_type == 'a':
            type = 'action'
            keys = action_keys
            cur = self.data.current_hash[action]
            prefix = '~ '
        elif entry_type == 'n':
            type = 'note'
            keys = note_keys
            cur = self.data.current_hash[note]
            prefix = '! '
        elif entry_type == 'j':
            item = False
            type = 'project'
            keys = common_keys + ['b']
            prefix = ''
        else:
            m0.append("unrecognized entry_type: %s" % entry_type)

        
        if len(m1+m2) == 0:
            if item:
                if 'r' in tsk and tsk['r'] != None:
                    m3.extend(self.data.get_rrule(tsk))
                if 'chrcode' in tsk and 't' in tsk:
                    sl = ["%s %s" % (tsk['chrcode'], tsk['t'])]
                else:
                    m3.append(
                "      Entry must begin with '+', '-', '.', '*' or '~'")
            else:
                sl = ['%s' % tsk['j']]
        for key in keys:
            if has(tsk, key):
                sl.append("@%s %s" % (key, tsk[key]))
        msg.extend(m0+m1+m2+m3)
        
        if len(msg) > 0:
            if self.show_errors(msg) == wx.ID_CANCEL:
                self.cancel()
                return()
            else:
                return()
        
        # no errors
        s = " ".join(sl)
        if mode == 'a':
            prep = 'to'
            slst =  m4 + ['', 'Append', '']
        elif mode == 'r':
            prep = 'in'
            slst =  m4 + ['', 'Replace', '']
            slst.append("    %s" % self.orig_entry)
            slst.extend(['',"with", ''])
        elif mode == 'c':
            prep = 'in'
            slst =  ['Create project containing']
        slst.append("    %s" % s)
        slst.extend(['', "%s '%s' ?"  % (prep, file)])
        if confirm_modify and self.confirm(slst) !=  wx.ID_YES:
            return()
        else:
            if mode == 'a':
                wx.CallAfter(self.data.addline2file, s, file)
            elif mode == 'r':
                wx.CallAfter(self.data.replacelineinfile, linenum, s, file)
            elif mode == 'c':
                wx.CallAfter(self.data.makefilewithline, s, file)
            wx.CallAfter(self.show_view, self.cmd, self.args)

    def show_cal(self):
        self.cur_detail = "etm %s    <Esc> or F2 returns to %s display    L/R arrows shift interval" % (version, self.display)
        self.detail_bar.SetLabel(self.cur_detail)
        if self.cal_advance < 0:
            color = cal_pastcolor
        elif self.cal_advance == 0:
            color = cal_currentcolor
        else:
            color = cal_futurecolor
        html = """\
<title>calendar</title>
<body text="%s" bgcolor="%s">
<pre>
%s
</pre>
""" % (main_fgcolor, color,  "\n".join(cal(self.cal_advance)))
        self.html = html
        self.prnt = html
        self.htmlwin.SetPage(self.html)
    
    def toggle_htmlcalendar(self):
        self.cal_advance = 0
        if self.show_calendar:
            self.show_calendar = False
            self.show_view(self.cmd, self.args)
        else:
            self.show_calendar = True
            self.show_help = False
            self.show_cal()

    def toggle_yesterday(self):
        """Used to simulate for testing auto date refresh, the effect of the setting the current date to yesterday."""
        if self.yesterday:
            self.yesterday = False
            get_today(datetime.date.today())
        else:
            self.yesterday = True
            self.data.today = get_today(datetime.date.today() - oneday)
            self.new_today = True
            self.show_view(self.cmd, self.args)

    
    def toggle_help(self):
        if self.show_help:
            self.show_help = False
            self.htmlwin.SetPage(self.currentpage)
        else:
            self.show_help = True
            self.show_calendar = False
            self.html = help_html % (main_fgcolor, main_bgcolor)
            self.prnt = help_html % (print_fgcolor, 'white')
            self.htmlwin.SetPage(self.html)
    
    def OnEntryBarChar(self, event):
        curr_id = self.FindFocus().GetId()
        keycode = event.GetKeyCode()
        if keycode == 17:         # quit
            print('onentrybarchar quit')
            self.OnQuit()
        if keycode == wx.WXK_TAB:
            p = self.entry_bar.GetInsertionPoint()
            s = self.entry_bar.GetString(0,p)
            m = lastfield_regex.match(s)
            if m:
                field = m.group(1)
                entry = m.group(2).strip()
                if entry:
                    regex = re.compile(r'^%s' % entry)
                    length = len(entry)
                else:
                    regex = re.compile(r'^.*')
                    length = 0
                if field == 'c':
                    contexts = [x for x in list(self.data.contexts) if regex.match(x)]
                    contexts.sort()
                    self.EntryBarInsert(self.getSelection('contexts',
                        contexts)[length:])
                elif field == 'k': # keyword
                    keywords = [x for x in list(self.data.keywords) if regex.match(x)]
                    keywords.sort()
                    self.EntryBarInsert(self.getSelection('keywords',
                        keywords)[length:])
                elif field == 'r': # repeats
                    repeats = [x for x in list(self.data.repeats) if regex.match(x)]
                    repeats.sort()
                    self.EntryBarInsert(self.getSelection('repeats',
                        repeats)[length:])
        elif keycode == wx.WXK_UP:
            if self.show_hist:
                self.show_history()
            else:
                self.show_templates()
                # self.EntryBarInsert(self.getSelection(
                #     'item templates', templates))
        elif keycode == wx.WXK_RETURN:
            self.EntryBarEnter(event)
        elif keycode == wx.WXK_F1:
            self.toggle_help()
        elif keycode == wx.WXK_F2:
            self.html = self.toggle_htmlcalendar()
        elif keycode == wx.WXK_F5:
            self.EntryBarInsert(self.getDate())
        elif keycode == 27: # Esc cancel
            if confirm_cancel_entry:
                self.cancel(['Cancel entry?'])
            else:
                self.cancel()
        else:
            event.Skip()
    
    def OnChar(self, event):
        curr_id = self.FindFocus().GetId()
        keycode = event.GetKeyCode()
        self.show_hist = False
        if self.show_calendar and keycode not in [ ord(','), ord('<'),
            wx.WXK_LEFT, ord('.'), ord('>'), wx.WXK_RIGHT, ord('/'),
            wx.WXK_F2, wx.WXK_UP, wx.WXK_DOWN ]:
            self.toggle_htmlcalendar()            
        if keycode == 17:         # Ctrl-Q quit
            print('onchar quit')
            self.OnQuit(event)
        elif keycode == 16:         # Ctrl-P print
            self.OnPrint(event)
        elif keycode == 19:         # Ctrl-S save selection to clipboard
            self.view2Clipboard()
        elif keycode == wx.WXK_F1:
            self.toggle_help()
        elif keycode == wx.WXK_F2:
            self.toggle_htmlcalendar()
        elif keycode == wx.WXK_F3:
            self.data.changed = True
            self.new_today = True
            self.show_view(self.cmd, self.args)
        elif keycode == wx.WXK_F4:
            dlg = wx.TextEntryDialog(
                self,
                "Enter an expression of the form 'date (+|-) string'\nwhere string is either a date or an integer followed by 'days',\ne.g., 'dec 1 + 90 days' or 'nov 30 - sep 1'.",
                    'etm date calculator', '')
            if dlg.ShowModal() == wx.ID_OK:
                s = dlg.GetValue()
                if s:
                    msg = date_calculator(str(s))
                    dlg = wx.MessageDialog(None, msg, 
                        'etm date calculator',
                        wx.OK )
                    dlg.ShowModal()
                    dlg.Destroy()
        elif keycode == wx.WXK_F6:
            self.toggle_yesterday()
        elif keycode == 27: # Esc
            if self.show_calendar:
                self.toggle_htmlcalendar()
            elif self.in_getresponse:
                self.cancel(['Do you really want to cancel?'])
            else:
                self.show_view(self.cmd, self.args)
                self.setprompt(True)
        elif keycode in [ord(','), ord('<'), wx.WXK_LEFT] and \
            (self.display != 'agenda' or self.show_calendar):
            if self.show_calendar:
                self.cal_advance -= 1
                self.show_cal()
            else:
                self.advance -= 1
                self.show_view(self.cmd, self.args)
        elif keycode in [ord('.'), ord('>'), wx.WXK_RIGHT] and \
            (self.display != 'agenda' or self.show_calendar):
            if self.show_calendar:
                self.cal_advance += 1
                self.show_cal()
            else:
                self.advance += 1
                self.show_view(self.cmd, self.args)
        elif keycode == ord('/') and (self.display != 'agenda'
            or self.show_calendar):
            if self.show_calendar:
                self.cal_advance = 0
                self.show_cal()
            else:
                self.advance = 0
                self.show_view(self.cmd, self.args)
        elif keycode == ord('A'):
            self.show_hist = True
            self.last_cmd = self.cmd
            self.cmd = 'A'
            self.function = 'agenda'
            self.getresponse("agenda options:",
                    "", "", agendaview_html)
        elif keycode in [ord('a'), wx.WXK_SPACE]:
            self.cmd = 'a'
            self.last_cmd = 'a'
            self.show_agenda()
        elif keycode == ord('B'):
            self.show_hist = True
            self.last_cmd = self.cmd
            self.cmd = 'B'
            self.function = 'busy'
            self.getresponse("busy/free options:",
                    "", "", busyview_html)
        elif keycode == ord('b'):
            self.cmd = 'b'
            self.show_busy()
        elif keycode == ord('c'):
            if self.cmd in ['a', 'l']:
                self.last_cmd = self.cmd
                self.data.show_idnums = True
                self.getPos()
                self.show_view(self.cmd, self.args)
                self.restorePos()
                self.data.show_idnums = False
                self.function = 'copy_entry'
                self.getresponse("id (prepended to title) of item to copy to clipboard:", "", "", "")
        elif keycode == ord('d'):
            self.html, self.prnt = sunmoon_html()
            self.showoutput(self.html)
        elif keycode == ord('D'):
            if self.cmd in ['a', 'l']:
                self.last_cmd = self.cmd
                self.data.show_idnums = True
                self.getPos()
                self.show_view(self.cmd, self.args)
                self.restorePos()
                self.data.show_idnums = False
                self.function = 'delete_item'
                self.getresponse("id (prepended to title) of item to delete:", '', '')
        elif keycode == ord('e'):
            self.entry_type = 'e'
            self.file = self.getFile('event')
            if self.file:
                self.function = 'new_entry'
                self.getresponse("options for new event:",
                        "* ", 'event',
                        "\n".join(event_html))
            else:
                self.cancel()
        elif keycode == ord('f'):
            if self.cmd in ['a', 'l']:
                self.last_cmd = self.cmd
                self.data.show_idnums = True
                self.getPos()
                self.show_view(self.cmd, self.args)
                self.restorePos()
                self.data.show_idnums = False
                self.function = 'mark_finished'
                self.getresponse("id (prepended to title) of task to mark finished:", '', "")
        elif keycode == ord('i'):
            if self.timer_paused:
                self.restart_timer()
            elif self.timer_running:
                self.interrupt_timer()
            else:
                self.function = 'start_timer'
                self.getresponse("initial entry for new action:", "~ ", 'action')
        elif keycode == ord('I'):
            self.entry_type = 'a'
            if self.timer_running:
                (date, minutes) = self.stop_timer()
                entry = "%s @d %s @p %s" % (self.timer_entry,
                        date, minutes)
            else:
                entry = "~ "
            self.file = self.getFile('action')
            if self.file:
                self.function = 'new_entry'
                self.getresponse("options for new action:",
                        "%s" % entry, 'action',
                        "\n".join(action_html))
            else:
                self.cancel()
        elif keycode == ord('j'):
            self.entry_type = 'j'
            self.file = self.getFile('project', 'create')
            if self.file:
                self.function = 'new_project'
                self.getresponse("options for new project:",
                        "", 'project',
                        "\n".join(project_html))
            else:
                self.cancel()
        elif keycode == ord('J'):
            self.entry_type = 'j'
            self.file = self.getFile('project', 'open')
            if self.file:
                self.open_project()
            else:
                self.cancel()
        elif keycode == ord('L'):
            self.show_hist = True
            self.last_cmd = self.cmd
            self.cmd = 'L'
            self.function = 'list'
            self.getresponse("list options:", "", "", listview_html)
        elif keycode == ord('l'):
            self.last_cmd = self.cmd
            self.cmd = 'l'
            self.show_list()
        elif keycode == ord('m'):
            if self.cmd in ['a', 'l']:
                self.last_cmd = self.cmd
                self.data.show_idnums = True
                self.getPos()
                self.show_view(self.cmd, self.args)
                self.restorePos()
                self.data.show_idnums = False
                self.function = 'edit_entry'
                self.getresponse("id (prepended to title) of item to modify:", "", "", "")
        elif keycode == ord('M'):
            if self.cmd in ['a', 'l']:
                self.last_cmd = self.cmd
                self.data.show_idnums = True
                self.getPos()
                self.show_view(self.cmd, self.args)
                self.restorePos()
                self.data.show_idnums = False
                self.function = 'edit_item'
                self.getresponse("id (prepended to title) of item to open in editor:", "", "", "")
        elif keycode == ord('n'):
            self.entry_type = 'n'
            self.file = self.getFile('note')
            if self.file:
                self.function = 'new_entry'
                self.getresponse("options for new note:",
                        "! ", 'note',
                        "\n".join(note_html))
            else:
                self.cancel()
        elif keycode == ord('N'):
            self.setmessage(newer(), True)
        elif keycode == ord('q'):
            html = self.alert_status()
            self.html = html % (main_fgcolor, main_bgcolor)
            self.prnt = html % (print_fgcolor, 'white')
            self.showoutput(self.html)
        elif keycode == ord('R'):
            self.show_hist = True
            self.last_cmd = self.cmd
            self.cmd = 'R'
            self.function = 'reckoning'
            self.getresponse("reckoning options:",
                    "", "", reckoningview_html)
        elif keycode == ord('r'):
            self.last_cmd = self.cmd
            self.cmd = 'r'
            self.show_reckoning()
        elif keycode == ord('t'):
            self.entry_type = 't'
            self.file = self.getFile('task')
            if self.file:
                self.function = 'new_entry'
                self.getresponse("options for new task:",
                        ". ", 'task',
                        "\n".join(task_html))
            else:
                self.cancel()
        elif keycode == ord('u'):
            if self.cmd in ['a', 'l']:
                self.last_cmd = self.cmd
                self.data.show_idnums = True
                self.getPos()
                self.show_view(self.cmd, self.args)
                self.restorePos()
                self.data.show_idnums = False
                self.function = 'mark_unfinished'
                self.getresponse("id (prepended to title) of task to mark unfinished:", '', "")
        elif keycode == ord('v'):
            self.verbose_toggle = not self.verbose_toggle
            self.show_view(self.cmd, self.args)
        elif keycode == ord('w'):
            html = weather_html()
            self.html = html % (main_fgcolor, main_bgcolor)
            self.prnt = html % (print_fgcolor, 'white')
            self.showoutput(self.html)
        else:
            event.Skip()
    
    functionHash = {
            'agenda' : show_agenda,
            'list' : show_list,
            'busy' : show_busy,
            'reckoning' : show_reckoning,
            'copy_entry' : copy_entry,
            'edit_entry' : edit_entry,
            'edit_item' : edit_item,
            'process_entry' : process_entry,
            'new_entry' : new_entry,
            'start_timer' : start_timer,
            'mark_finished' : mark_finished,
            'mark_unfinished' : mark_unfinished,
            'delete_item' : delete_item,
            'new_project' : new_project,
            'open_project' : open_project,
            }

    
    typeHash = {
            'e': ['event', event_html, event_keys, '*'],
            't': ['task', task_html, task_keys, '.'],
            'a': ['action', action_html, action_keys, '~'],
            'n': ['note', note_html, note_keys, '!'],
            }

def weather_html():
    lst = getweather()
    ret = ['<title>yahoo weather</title>',
        '<body text="%s" bgcolor="%s">',
            '<pre>']
    for line in lst:
        ret.append(line)
    ret.append("</pre></body>")
    html = "\n".join(ret)
    return(html)


def sunmoon_html():
    lst = getsunmoon()
    po = ['<title>USNO sun and moon data</title>',
        '<body text="%s">' % (print_fgcolor),
            '<pre>']
    ret = ['<title>USNO sun and moon data</title>',
        '<body text="%s" bgcolor="%s">' % (main_fgcolor, main_bgcolor),
            '<pre>']
    for line in lst:
        line = line.rstrip()
        if line:
            ret.append(line)
            po.append(line)
    ret.append("</pre></body>")
    po.append("</pre></body>")
    html = "\n".join(ret)
    prnt = "\n".join(po)
    return(html, prnt)

class App(wx.App):
    def OnInit(self):
        wx.lib.colourdb.updateColourDB()
        self.SetAppName('etm')
        self.SetClassName('etm')
        self.SetVendorName('etm')
        self.frame = MyFrame(parent=None, id=-1, title="etm")
        self.frame.Show()
        self.SetTopWindow(self.frame)
        return(True)

def main():
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    # fo = open(etm_redirect, 'w')
    # fo.write("### starting %s ###\n" % now)
    # fo.close()
    app = App(redirect=False)
    # app = App()
    app.SetAppName('etm')
    app.MainLoop()

if __name__ == "__main__":
    main()
