#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import os
# import sys
import re
import uuid
from copy import deepcopy
import subprocess
from dateutil.tz import tzlocal
import codecs

import logging
import logging.config

logger = logging.getLogger()

import platform

if platform.python_version() >= '3':
    import tkinter
    from tkinter import (
        BOTH,
        Button,
        Canvas,
        CENTER,
        CURRENT,
        END,
        Entry,
        FLAT,
        Frame,
        INSERT,
        IntVar,
        Label,
        LEFT,
        Menu,
        OptionMenu,
        PanedWindow,
        PhotoImage,
        RAISED,
        Scrollbar,
        StringVar,
        Tk,
        TOP,
        Toplevel,
        W,
        X,
    )
    from tkinter import ttk
    from tkinter import font as tkFont
    utf8 = lambda x: x
    unicode = str

else:
    import Tkinter as tkinter
    from Tkinter import (
        BOTH,
        Button,
        Canvas,
        CENTER,
        CURRENT,
        END,
        Entry,
        FLAT,
        Frame,
        INSERT,
        IntVar,
        Label,
        LEFT,
        Menu,
        OptionMenu,
        PanedWindow,
        PhotoImage,
        RAISED,
        Scrollbar,
        StringVar,
        Tk,
        TOP,
        Toplevel,
        W,
        X,
    )
    import ttk
    import tkFont

    def utf8(s):
        return s

tkversion = tkinter.Tcl().eval('info patchlevel')

import etmTk.data as data

from dateutil.parser import parse

from calendar import Calendar

from decimal import Decimal

from etmTk.data import (
    init_localization, fmt_weekday, fmt_dt, str2hsh, tstr2SCI, leadingzero, relpath, s2or3, send_mail, send_text, get_changes, checkForNewerVersion, datetime2minutes, calyear, expand_template, id2Type, get_current_time, windoz, mac, setup_logging, gettz, commandShortcut, rrulefmt, tree2Text, date_calculator, AFTER, export_ical_item, export_ical_active, fmt_time, fmt_period, TimeIt, getReportData, getFileTuples, getAllFiles, updateCurrentFiles, FINISH, availableDates, syncTxt, update_subscription, _)

from etmTk.dialog import MenuTree, Timer, ReadOnlyText, MessageWindow, TextDialog, OptionsDialog, GetInteger, GetDateTime, GetString, FileChoice, STOPPED, PAUSED, RUNNING, BGCOLOR, ONEDAY, ONEMINUTE, SimpleEditor

# from etmTk.edit import SimpleEditor

# import gettext

# _ = gettext.gettext


from datetime import datetime, time, date

ETM = "etm"

# STOPPED = _('stopped')
# PAUSED = _('paused')
# RUNNING = _('running')

FILTER = _("filter")
FILTERCOLOR = "gray"

# Views #
AGENDA = _('Agenda')

DAY = _('Day')
WEEK = _("Week")
MONTH = _("Month")

PATH = _('Path')
KEYWORD = _('Keyword')
TAG = _('Tag')

NOTE = _('Note')
CUSTOM = _("Custom")

CALENDARS = _("Calendars")

COPY = _("Copy")
EDIT = _("Edit")
DELETE = _("Delete")

FILE = _("File")
NEW = _("New")
TIMER = _("Timer")
OPEN = _("Open")
VIEW = _("View")
ITEM = _("Item")
TOOLS = _("Tools")
HELP = _("Help")

MAKE = _("Make report")
PRINT = _("Print")
EXPORTTEXT = _("Export report in text format ...")
EXPORTCSV = _("Export report in CSV format ...")
SAVESPECS = _("Save changes to report specifications")

CLOSE = _("Close")

SEP = "----"

# ACTIVEFILL = "#FAFCAC"
ACTIVEFILL = "#FCFCD9"
ACTIVEOUTLINE = "gray40"

DEFAULTFILL = "#D4DCFC"  # blue
OTHERFILL = "#C7EDC8"  # green

BUSYOUTLINE = ""

CONFLICTFILL = "#C1C4C9"
# CURRENTFILL = "#FCF2F0"
CURRENTFILL = "#DCEAFC"
# CURRENTLINE = "#D053E0"
CURRENTLINE = "#3C3FDE"
OUTSIDELINE = "#E0535C"
LASTLTR = re.compile(r'([a-z])$')
LINECOLOR = "gray80"

# OCCASIONFILL = "gray96"
OCCASIONFILL = "gray92"

this_dir, this_filename = os.path.split(__file__)
USERMANUAL = os.path.normpath(os.path.join(this_dir, "help", "UserManual.html"))
ICONSETTINGS = os.path.normpath(os.path.join(this_dir, "icons", "icon_settings.gif"))
ICONPLUS = os.path.normpath(os.path.join(this_dir, "icons", "icon_plus.gif"))
# ICONLOGO = os.path.normpath(os.path.join(this_dir, "icons", "etmlogo.gif"))

class App(Tk):
    def __init__(self, path=None):
        Tk.__init__(self)

        self.minsize(400, 460)
        self.uuidSelected = None
        self.timerItem = None
        self.loop = loop
        self.monthly_calendar = Calendar()
        self.activeAlerts = []
        self.configure(background=BGCOLOR)
        self.option_add('*tearOff', False)
        self.menu_lst = []
        self.menutree = MenuTree()
        self.chosen_day = None
        self.active_date = None
        self.canvas_date = None
        self.busy_info = None
        self.weekly = False
        self.monthly = False
        self.specsModified = False
        self.active_tree = {}
        self.protocol("WM_DELETE_WINDOW", self.quit)
        root = "_"

        self.week_height = 80
        self.month_height = 260

        self.options = loop.options

        tkfixedfont = tkFont.nametofont("TkFixedFont")
        if 'fontsize_fixed' in self.options and self.options['fontsize_fixed']:
            tkfixedfont.configure(size=self.options['fontsize_fixed'])
        logger.info("using fixedfont size: {0}".format(tkfixedfont.actual()['size']))
        self.tkfixedfont = tkfixedfont

        tktreefont = tkFont.nametofont("TkDefaultFont")
        treefontfamily = tktreefont['family']
        if 'fontsize_tree' in self.options and self.options['fontsize_tree']:
            tktreefont.configure(size=self.options['fontsize_tree'])
        logger.info("using treefont size: {0}".format(tktreefont.actual()['size']))
        self.tktreefont = tktreefont

        ef = "%a %b %d"
        if 'ampm' in loop.options and loop.options['ampm']:
            self.efmt = "%I:%M%p {0}".format(ef)
        else:
            self.efmt = "%H:%M {0}".format(ef)

        self.default_calendars = deepcopy(loop.options['calendars'])

        # create the root node for the menu tree
        self.menutree.create_node(root, root)

        # leaf: (parent, (option, [accelerator])
        self.outline_depths = {}
        for view in DAY, WEEK, MONTH, TAG, KEYWORD, NOTE, PATH:
            # set all to the default
            logger.debug('Setting depth for {0} to {1}'.format(view, loop.options['outline_depth']))
            self.outline_depths[view] = loop.options['outline_depth']
        # set CUSTOM to 0
        self.outline_depths[AGENDA] = 0
        self.outline_depths[CUSTOM] = 0

        self.topbar = topbar = Frame(self, bd=0, relief="flat", highlightbackground=BGCOLOR, background=BGCOLOR, takefocus=False)
        topbar.pack(side="top", fill="x", expand=0, padx=0, pady=0)

        self.box_value = StringVar()
        self.custom_box = ttk.Combobox(self.topbar, textvariable=self.box_value, font=self.tkfixedfont)

        self.statusbar = Frame(self, bd=0, relief="flat", highlightbackground=BGCOLOR, background=BGCOLOR, takefocus=False)
        self.statusbar.pack(side="bottom", fill="x", expand=0, padx=4, pady=2)

        self.topwindow = topwindow = PanedWindow(self, orient="vertical", sashwidth=4, sashrelief='flat')
        self.topwindow.pack(side="top", fill=BOTH, expand=1)


        self.toppane = toppane = Frame(
            topwindow, bd=0, relief="flat",
            highlightthickness=0,
            highlightbackground=BGCOLOR, background=BGCOLOR)
        self.toppane.pack(side="top", fill=BOTH, expand=1)

        self.canvas = Canvas(
            self.toppane, background="white", bd=1, relief="flat",
            highlightthickness=2, highlightbackground=BGCOLOR)
        self.canvas.pack(fill=BOTH, expand=1, padx=0, pady=0)


        self.botwindow = botwindow = PanedWindow(topwindow, orient="vertical", sashwidth=4, sashrelief='flat')
        topwindow.add(botwindow)

        self.treepane = treepane = Frame(
            botwindow, bd=0, relief="flat",
            highlightthickness=2,
            highlightbackground=BGCOLOR,
            background=BGCOLOR)
        botwindow.add(treepane, padx=0, pady=0, stretch="first")

        self.content = ReadOnlyText(
            botwindow, font=self.tkfixedfont,
            wrap="word", padx=3, bd=2, relief="sunken",
            height=loop.options['details_rows'],
            width=46, takefocus=False)
        botwindow.add(self.content, padx=3, pady=0, stretch="never")


        self.tree = ttk.Treeview(treepane, show='tree', columns=["#1", "#2"], selectmode='browse')
        self.tree.pack(fill="both", expand=1, padx=4, pady=0)


        self.canvas.bind('<Button-1>', (lambda e: self.selectId(event=e, d=0)))
        self.canvas.bind("<Control-Button-1>", self.on_select_item)
        self.canvas.bind("<Double-1>", self.on_select_item)

        self.canvas.bind("<Return>", lambda e: self.on_activate_item(event=e))
        self.canvas.bind('<Left>', (lambda e: self.priorWeekMonth(event=e)))
        self.canvas.bind('<Right>', (lambda e: self.nextWeekMonth(event=e)))
        self.canvas.bind('<Up>', (lambda e: self.selectId(event=e, d=-1)))
        self.canvas.bind('<Down>', (lambda e: self.selectId(event=e, d=1)))
        self.canvas.bind('<space>', self.goHome)

        self.canvas.bind("b", lambda event: self.after(AFTER, self.showBusyPeriods))
        self.canvas.bind("f", lambda event: self.after(AFTER, self.showFreePeriods))

        # main menu
        self.menubar = menubar = Menu(self)
        menu = _("Menubar")
        self.add2menu(root, (menu,))

        # File menu
        filemenu = Menu(menubar, tearoff=0)
        path = FILE
        self.add2menu(menu, (path, ))

        self.newmenu = newmenu = Menu(filemenu, tearoff=0)
        filemenu.add_cascade(label=NEW, menu=newmenu)

        self.add2menu(path, (NEW, ))
        path = NEW

        label = _("Item")
        l = "N"
        c = "n"
        newmenu.add_command(label=label, command=self.newItem)
        # self.bindTop("n", self.newItem)
        self.bindTop(c, lambda e: self.after(AFTER, self.newItem(e)))
        self.canvas.bind(c, lambda e: self.after(AFTER, self.newItem(e)))

        newmenu.entryconfig(0, accelerator=l)
        self.add2menu(path, (label, l))

        l = "Shift-N"
        c = "N"
        label = _("File")
        newmenu.add_command(label=label, command=self.newFile)
        self.bindTop(c, self.newFile)
        newmenu.entryconfig(1, accelerator=l)
        self.add2menu(path, (label, l))

        path = FILE

        self.timermenu = timermenu = Menu(filemenu, tearoff=0)
        filemenu.add_cascade(label=TIMER, menu=timermenu)
        self.add2menu(path, (TIMER, ))
        path = TIMER

        self.actionTimer = Timer(self, options=loop.options)

        label = _("Start Action Timer")
        l = "T"
        c = 't'
        timermenu.add_command(label=label, command=self.actionTimer.selectTimer)
        self.bindTop(c, self.actionTimer.selectTimer)
        timermenu.entryconfig(0, accelerator=l)
        self.add2menu(path, (label, l))

        label = _("Finish Action Timer")
        l = "Shift-T"
        c = "T"
        timermenu.add_command(label=label, command=self.finishActionTimer)
        self.bind(c, self.finishActionTimer)
        timermenu.entryconfig(1, accelerator=l)
        self.add2menu(path, (label, l))

        label = _("Toggle Current Timer")
        l = "I"
        c = 'i'
        timermenu.add_command(label=label, command=self.actionTimer.toggleCurrent)
        self.bindTop(c, self.actionTimer.toggleCurrent)
        timermenu.entryconfig(2, accelerator=l)
        self.add2menu(path, (label, l))

        label = _("Delete Action Timer")
        l = "Shift-I"
        c = "I"
        timermenu.add_command(label=label, command=self.actionTimer.deleteTimer)
        self.bind(c, self.actionTimer.deleteTimer)
        timermenu.entryconfig(3, accelerator=l)
        self.add2menu(path, (label, l))

        self.actionTimer.updateMenu()

        path = FILE

        # Open
        openmenu = Menu(filemenu, tearoff=0)
        self.add2menu(path, (OPEN, ))
        path = OPEN

        l = "Shift-F"
        c = "F"
        label = _("Data file ...")
        openmenu.add_command(label=label, command=self.editData)
        self.bindTop(c, self.editData)
        openmenu.entryconfig(0, accelerator=l)
        self.add2menu(path, (label, l))

        l = "Shift-C"
        c = "C"
        label = _("Configuration file ...")
        openmenu.add_command(label=label, command=self.editCfgFiles)
        self.bindTop(c, self.editCfgFiles)
        openmenu.entryconfig(1, accelerator=l)
        self.add2menu(path, (label, l))

        l = "Shift-P"
        c = "P"
        label = "preferences"
        openmenu.add_command(label=label, command=self.editConfig)
        self.bindTop(c, self.editConfig)

        openmenu.entryconfig(2, accelerator=l)
        self.add2menu(path, (label, l))

        l = "Shift-S"
        c = "S"
        file = loop.options['scratchpad']
        label = relpath(file, loop.options['etmdir'])
        openmenu.add_command(label=label, command=self.editScratch)
        self.bindTop(c, self.editScratch)
        openmenu.entryconfig(3, accelerator=l)
        self.add2menu(path, (label, l))

        filemenu.add_cascade(label=OPEN, menu=openmenu)

        path = FILE

        filemenu.add_separator()
        self.add2menu(path, (SEP, ))

        # quit
        l, c = commandShortcut('q')
        label = _("Quit")
        filemenu.add_command(label=label, underline=0,
                             command=self.quit)
        self.bind(c, self.quit)  # w
        self.add2menu(path, (label, l))

        menubar.add_cascade(label=path, underline=0, menu=filemenu)
        self.toolsmenu = viewmenu = Menu(menubar, tearoff=0)

        self.viewmenu = viewmenu = Menu(menubar, tearoff=0)
        path = VIEW
        self.add2menu(menu, (path, ))

        # # agenda
        # l = label = AGENDA
        # toolsmenu.add_command(label=label, command=self.agendaView)

        self.vm_options = [[AGENDA, 'a'],
                           [DAY, 'd'],
                           [WEEK, 'w'],
                           [MONTH, 'm'],
                           [TAG, 't'],
                           [KEYWORD, 'k'],
                           [PATH, 'p'],
                           [NOTE, 'n'],
                           [CUSTOM, 'c'],
                           ]

        self.view2cmd = {'a': self.agendaView,
                         'd': self.dayView,
                         'm': self.showMonthly,
                         'p': self.pathView,
                         'k': self.keywordView,
                         'n': self.noteView,
                         't': self.tagView,
                         'c': self.customView,
                         'w': self.showWeekly}

        self.viewname2cmd = {}

        self.view = self.vm_options[0][0]
        self.currentView = StringVar(self)
        self.currentView.set(self.view)

        self.vm_opts = [x[0] for x in self.vm_options]
        for i in range(len(self.vm_options)):
            label = self.vm_options[i][0]
            k = self.vm_options[i][1]
            if label == DAY:
                continue
            elif label == "-":
                self.viewmenu.add_separator()
                # self.add2menu(VIEW, (SEP, ))
            else:
                l, c = commandShortcut(k)
                viewmenu.add_command(label=label, command=self.view2cmd[k])
                self.bind(c, lambda e, x=k: self.after(AFTER, self.view2cmd[x]))
                viewmenu.entryconfig(i, accelerator=l)
                self.add2menu(path, (label, l))


        viewmenu.add_separator()
        self.add2menu(path, (SEP, ))

        # apply filter
        l, c = commandShortcut('f')
        label = _("Set outline filter")
        viewmenu.add_command(label=label, underline=1, command=self.setFilter)
        self.bind(c, self.setFilter)

        viewmenu.entryconfig(10, accelerator=l)
        self.add2menu(path, (label, l))

        # clear filter
        l = "Shift-Ctrl-F"
        label = _("Clear outline filter")
        viewmenu.add_command(label=label, underline=1, command=self.clearFilter)

        viewmenu.entryconfig(11, accelerator=l)
        self.add2menu(path, (label, l))

        # toggle showing labels
        l = "L"
        c = "l"
        label = _("Toggle labels")
        viewmenu.add_command(label=label, underline=1, command=self.toggleLabels)
        self.bindTop(c, self.toggleLabels)

        viewmenu.entryconfig(12, accelerator=l)
        self.add2menu(path, (label, l))

        # expand to depth
        l = "O"
        c = "o"
        label = _("Set outline depth")
        viewmenu.add_command(label=label, underline=1, command=self.expand2Depth)
        self.bindTop(c, self.expand2Depth)

        viewmenu.entryconfig(13, accelerator=l)
        self.add2menu(path, (label, l))

        # toggle showing finished
        l = "X"
        c = "x"
        label = _("Toggle displayings finished")
        viewmenu.add_command(label=label, underline=1, command=self.toggleFinished)
        self.bindTop(c, self.toggleFinished)
        viewmenu.entryconfig(14, accelerator=l)
        self.add2menu(path, (label, l))

        menubar.add_cascade(label=path, underline=0, menu=viewmenu)

        # Item menu
        self.em_options = [
            [_('Copy'), 'c'],
            [_('Delete'), 'd'],
            [_('Edit'), 'e'],
            [_('Edit file'), 'E'],
            [_('Finish'), 'f'],
            [_('Move'), 'm'],
            [_('Reschedule'), 'r'],
            [_('Schedule new'), 'R'],
            [_('Klone as timer'), 'k'],
            [_('Open link'), 'g'],
            [_('Show user details'), 'u'],
            ]
        self.itemmenu = itemmenu = Menu(menubar, tearoff=0)
        self.itemmenu.bind("<Escape>", self.closeItemMenu)
        self.itemmenu.bind("<FocusOut>", self.closeItemMenu)
        path = ITEM
        self.add2menu(menu, (path, ))
        self.edit2cmd = {
            'c': self.copyItem,
            'd': self.deleteItem,
            'e': self.editItem,
            'E': self.editItemFile,
            'f': self.finishItem,
            'm': self.moveItem,
            'r': self.rescheduleItem,
            'R': self.scheduleNewItem,
            'g': self.openWithDefault,
            'u': self.showUserDetails,
            'k': self.kloneTimer}
        self.em_opts = [x[0] for x in self.em_options]
        for i in range(len(self.em_options)):
            label = self.em_options[i][0]
            k = self.em_options[i][1]
            if k == 'd':
                l = "BackSpace"
                c = "<BackSpace>"
            elif k == 'E':
                l = "Shift-E"
                c = "E"
            elif k == 'R':
                l = "Shift-R"
                c = "R"
            else:
                l = k.upper()
                c = k
            itemmenu.add_command(label=label, underline=0, command=self.edit2cmd[k])
            if k == 'f':
                self.tree.bind(c, self.edit2cmd[k])
            else:
                self.bindTop(c, self.edit2cmd[k])

            itemmenu.entryconfig(i, accelerator=l)
            self.add2menu(path, (label, l))
        menubar.add_cascade(label=path, underline=0, menu=itemmenu)

        # tools menu
        path = TOOLS
        self.add2menu(menu, (path, ))
        self.toolsmenu = toolsmenu = Menu(menubar, tearoff=0)

        # go home
        l = "Home"
        label = _("Home")
        toolsmenu.add_command(label=label, command=self.goHome)

        toolsmenu.entryconfig(0, accelerator=l)
        self.add2menu(path, (label, l))
        self.bindTop('<Home>', self.goHome)

        # go to date
        l = "J"
        c = "j"
        label = _("Jump to date")
        toolsmenu.add_command(label=label, command=self.goToDate)
        self.bindTop(c, self.goToDate)

        toolsmenu.entryconfig(1, accelerator=l)
        self.add2menu(path, (label, l))

        toolsmenu.add_separator()  # 2
        self.add2menu(path, (SEP, ))

        # show alerts
        l = "A"
        c = "a"
        label = _("Show remaining alerts for today")
        toolsmenu.add_command(label=label, underline=1, command=self.showAlerts)
        self.bindTop(c, self.showAlerts)

        toolsmenu.entryconfig(3, accelerator=l)
        self.add2menu(path, (label, l))

        l = "B"
        c = 'b'
        label = _("List busy times in week/month")
        toolsmenu.add_command(label=label, underline=5, command=self.showBusyPeriods)

        toolsmenu.entryconfig(4, accelerator=l)
        self.add2menu(path, (label, l))

        l = "F"
        c = 'f'
        label = _("List free times in week/month")
        toolsmenu.add_command(label=label, underline=5, command=self.showFreePeriods)

        toolsmenu.entryconfig(5, accelerator=l)
        # set binding in showWeekly
        self.add2menu(path, (label, l))


        # date calculator
        l = "Shift-D"
        c = "D"
        label = _("Date and time calculator")
        toolsmenu.add_command(label=label, underline=12, command=self.dateCalculator)
        self.bindTop(c, self.dateCalculator)

        toolsmenu.entryconfig(6, accelerator=l)
        self.add2menu(path, (label, l))

        # available date calculator
        l = "Shift-A"
        c = "A"
        label = _("Available dates calculator")
        toolsmenu.add_command(label=label, underline=12, command=self.availableDateCalculator)
        self.bindTop(c, self.availableDateCalculator)

        toolsmenu.entryconfig(7, accelerator=l)
        self.add2menu(path, (label, l))

        l = "Shift-Y"
        c = "Y"

        label = _("Yearly calendar")
        toolsmenu.add_command(label=label, underline=8, command=self.showCalendar)
        self.bindTop(c, self.showCalendar)

        toolsmenu.entryconfig(8, accelerator=l)
        self.add2menu(path, (label, l))

        toolsmenu.add_separator() # 9
        self.add2menu(path, (SEP, ))

        # popup active tree
        l = "S"
        c = "s"
        label = _("Show outline as text")
        toolsmenu.add_command(label=label, underline=1, command=self.popupTree)
        self.bindTop(c, self.popupTree)

        toolsmenu.entryconfig(10, accelerator=l)
        self.add2menu(path, (label, l))

        # print active tree
        l = "P"
        c = "p"
        label = _("Print outline")
        toolsmenu.add_command(label=label, underline=1, command=self.printTree)
        self.bindTop("p", self.printTree)
        toolsmenu.entryconfig(11, accelerator=l)
        self.add2menu(path, (label, l))

        # export
        l = "Shift-X"
        c = "X"
        label = _("Export to iCal")
        toolsmenu.add_command(label=label, underline=1, command=self.exportToIcal)
        self.bind(c, self.exportToIcal)

        toolsmenu.entryconfig(12, accelerator=l)
        self.add2menu(path, (label, l))

        # update subscriptions
        l = "Shift-M"
        c = "M"
        label = _("Update calendar subscriptions")
        toolsmenu.add_command(label=label, underline=1, command=self.updateSubscriptions)
        self.bind(c, self.updateSubscriptions)

        toolsmenu.entryconfig(13, accelerator=l)
        self.add2menu(path, (label, l))

        # load data
        l = "Shift-L"
        c = "L"
        label = _("Reload data from files")
        toolsmenu.add_command(label=label, underline=1, command=loop.loadData)
        self.bind(c, loop.loadData)

        toolsmenu.entryconfig(14, accelerator=l)
        self.add2menu(path, (label, l))

        # changes
        if loop.options['vcs_system']:

            l = 'Shift-H'
            c = 'H'
            label = _("History of changes")
            toolsmenu.add_command(label=label, underline=1, command=self.showChanges)
            self.bind(c, lambda event: self.after(AFTER, self.showChanges))

            toolsmenu.entryconfig(15, accelerator=l)
            self.add2menu(path, (label, l))

        menubar.add_cascade(label=path, menu=toolsmenu, underline=0)

        self.toolsmenu.entryconfig(1, state="disabled")
        for i in range(4, 6):
            self.toolsmenu.entryconfig(i, state="disabled")

        # report
        path = CUSTOM
        self.add2menu(menu, (path, ))
        self.custommenu = reportmenu = Menu(menubar, tearoff=0)

        self.rm_options = [[MAKE, 'm'],
                           [EXPORTTEXT, 't'],
                           [EXPORTCSV, 'x'],
                           [SAVESPECS, 'w']]

        self.rm2cmd = {'m': self.makeReport,
                       't': self.exportText,
                       'x': self.exportCSV,
                       'w': self.saveSpecs}

        self.rm_opts = [x[0] for x in self.rm_options]

        for i in range(len(self.rm_options)):
            label = self.rm_options[i][0]
            k = self.rm_options[i][1]
            l = k.upper()
            c = k

            reportmenu.add_command(label=label, underline=0, command=self.rm2cmd[k])
            reportmenu.entryconfig(i, state="disabled")

        self.add2menu(CUSTOM, (_("Create and display selected report"), "Return"))
        self.add2menu(CUSTOM, (_("Export report in text format ..."), "Ctrl-T"))
        self.add2menu(CUSTOM, (_("Export report in csv format ..."), "Ctrl-X"))
        self.add2menu(CUSTOM, (_("Save changes to report specifications"), "Ctrl-W"))
        self.add2menu(CUSTOM, (_("Expand report list"), "Down"))

        menubar.add_cascade(label=path, menu=reportmenu, underline=0)

        # help
        helpmenu = Menu(menubar, tearoff=0)
        path = HELP
        self.add2menu(menu, (path, ))

        # search is built in
        self.add2menu(path, (_("Search"), ))

        label = _("Shortcuts")
        helpmenu.add_command(label=label, underline=1, accelerator="?", command=self.showShortcuts)
        self.add2menu(path, (label, "?"))
        self.bindTop("?", self.showShortcuts)

        label = _("User manual")
        helpmenu.add_command(label=label, underline=1, accelerator="F1", command=self.help)
        self.add2menu(path, (label, "F1"))
        self.bind("<F1>", lambda e: self.after(AFTER, self.help))

        label = _("About")
        helpmenu.add_command(label="About", accelerator="F2", command=self .about)
        self.bind("<F2>", self.about)
        self.add2menu(path, (label, "F2"))

        # check for updates

        label = _("Check for update")
        helpmenu.add_command(label=label, underline=1, accelerator="F3", command=self.checkForUpdate)
        self.add2menu(path, (label, "F3"))
        self.bind("<F3>", lambda e: self.after(AFTER, self.checkForUpdate))

        menubar.add_cascade(label="Help", menu=helpmenu)

        self.config(menu=menubar)

        self.history = []
        self.index = 0
        self.count = 0
        self.count2id = {}
        self.now = get_current_time()
        self.today = self.now.date()

        self.popup = ''
        self.value = ''
        self.firsttime = True
        self.mode = 'command'   # or edit or delete
        self.item_hsh = {}
        self.depth2id = {}
        self.prev_week = None
        self.next_week = None
        self.curr_week = None
        self.week_beg = None
        self.itemSelected = None
        self.uuidSelected = None
        self.dtSelected = None
        self.rowSelected = None

        self.currentTime = StringVar(self)
        self.currentTime.set("")

        # self.title(ETM)
        self.title(self.currentTime.get())

        self.columnconfigure(0, minsize=300, weight=1)
        self.rowconfigure(1, weight=2)

        # self.etmlogo = PhotoImage(file=ICONLOGO)
        # self.iconphoto(True, self.etmlogo)

        # report
        self.custom_box.bind("<<ComboboxSelected>>", self.newselection)
        self.bind("<Return>", self.makeReport)
        self.bind("<Control-q>", self.quit)
        self.saved_specs = ['']
        self.getSpecs()
        if self.specs:
            self.value_of_combo = self.specs[0]
            self.custom_box['values'] = self.specs
            self.custom_box.current(0)
            self.saved_specs = deepcopy(self.specs)
        self.custom_box.configure(width=30, background=BGCOLOR, takefocus=False)


        iconsize = "22"
        self.settingsIcon = PhotoImage(file=ICONSETTINGS)
        self.settingsbutton = Button(
            topbar, command=self.selectCalendars,
            highlightbackground=BGCOLOR,
            bg=BGCOLOR, pady=0, bd=0,
            highlightthickness=0, takefocus=False
        )
        self.settingsbutton.config(image=self.settingsIcon, width=iconsize, height=iconsize)
        self.settingsbutton.pack(side="left", padx=4, pady=2)


        self.newIcon = PhotoImage(file=ICONPLUS)
        self.newbutton = Button(topbar, command=self.newItem, highlightbackground=BGCOLOR, bg=BGCOLOR, pady=0, highlightthickness=0, takefocus=False)
        self.newbutton.config(image=self.newIcon, width=iconsize, height=iconsize)
        self.newbutton.pack(side="right", padx=4, pady=2)

        windowtitle = Label(topbar, textvariable=self.currentView, bd=1, relief="flat",  padx=8, pady=0)
        windowtitle.pack(side="left")
        windowtitle.configure(background=BGCOLOR)
        self.currentView.set("Agenda")


        # filter
        self.filterValue = StringVar(self)
        self.filterValue.set('')
        self.filterValue.trace_variable("w", self.filterView)

        self.fltr = Entry(topbar, textvariable=self.filterValue, width=14, highlightbackground=BGCOLOR, bg=BGCOLOR, takefocus=False)
        self.fltr.pack(side="left", padx=0, expand=1, fill=X)
        self.fltr.bind("<FocusIn>", self.setFilter)
        self.fltr.bind("<Escape>", self.clearFilter)
        self.fltr.bind('<Tab>', self.leaveFilter)
        self.bind("<Shift-Control-F>", self.clearFilter)
        self.filter_active = False

        self.weekly = False

        self.col2_width = tktreefont.measure('abcdgklmprtuX')
        self.col3_width = tktreefont.measure('10:30pm ~ 11:30pmX')
        self.text_width = 260
        logger.info('column widths: {0}, {1}, {2}'.format(self.text_width, self.col2_width, self.col3_width))
        self.tree.column('#0', minwidth=140, width=self.text_width, stretch=1)
        self.labels = False
        # don't show the labels column to start with by setting width=0
        self.tree.column('#1', minwidth=0, width=0, stretch=0, anchor='center')
        self.tree.column('#2', width=self.col3_width, stretch=0, anchor='center')
        self.tree.bind('<<TreeviewSelect>>', self.OnSelect)
        self.tree.bind('<Double-1>', self.OnActivate)
        self.tree.bind('<Return>', self.OnActivate)
        self.tree.bind('<Control-Down>', self.nextItem)
        self.tree.bind('<Control-Up>', self.prevItem)


        for t in tstr2SCI:
            self.tree.tag_configure(t, foreground=tstr2SCI[t][1])

        self.date2id = {}
        self.root = ('', '_')

        self.content.bind('<Escape>', self.cleartext)
        self.content.bind('<Tab>', self.focus_next_window)
        self.content.bind("<FocusIn>", self.editItem)


        self.pendingAlerts = IntVar(self)
        self.pendingAlerts.set(0)
        self.pending = Button(self.statusbar,
                              textvariable=self.pendingAlerts,
                              command=self.showAlerts,
                              highlightbackground=BGCOLOR,
                              bg=BGCOLOR, highlightthickness=0, takefocus=False,
                              width=4, pady=2,
                              )
        self.pending.pack(side="right", expand=0, padx=2, pady=2)

        self.timerStatus = StringVar(self)
        self.timerStatus.set("")
        self.timer_status = timer_status = Label(self.statusbar, textvariable=self.timerStatus, bd=0, relief="flat", anchor=W, justify=LEFT, padx=2, pady=0)
        timer_status.pack(side="right", expand=1, fill=X, padx=6)
        timer_status.configure(background=BGCOLOR, highlightthickness=0)

        self.timerTitle = StringVar(self)
        self.timerTitle.set("")
        timer_title = Label(self.statusbar, textvariable=self.timerTitle, bd=0, relief="flat", anchor=W, justify=LEFT, padx=2, pady=0)

        timer_title.pack(side="left", expand=1, fill=X, padx=0)
        timer_title.configure(background=BGCOLOR, highlightthickness=0)


        # set cal_regex here and update it in updateCalendars
        self.cal_regex = None
        if loop.calendars:
            cal_pattern = r'^%s' % '|'.join(
                [x[2] for x in loop.calendars if x[1]])
            self.cal_regex = re.compile(cal_pattern)
            logger.debug("cal_pattern: {0}".format(cal_pattern))

        self.default_regex = None
        if 'calendars' in loop.options and loop.options['calendars']:
            calendars = loop.options['calendars']
            default_pattern = r'^%s' % '|'.join(
                [x[2] for x in calendars if x[1]])
            self.default_regex = re.compile(default_pattern)

        self.add2menu(root, (EDIT, ))
        self.add2menu(EDIT, (_("Show completions"), "Ctrl-Space"))
        self.add2menu(EDIT, (_("Cancel"), "Escape"))
        self.add2menu(EDIT, (FINISH, "Ctrl-W"))

        # start clock
        self.updateClock()
        self.year_month = [self.today.year, self.today.month]
        # showView will be called from updateClock
        self.updateAlerts()
        self.etmgeo = os.path.normpath(os.path.join(loop.options['etmdir'], ".etmgeo"))
        self.restoreGeometry()
        self.etmtimers = os.path.normpath(os.path.join(loop.options['etmdir'], ".etmtimers"))
        self.tree.focus_set()

    def bindTop(self, c, cmd, e=None):
        if e and e.char != c:
            # ignore Control-c
            return
        self.tree.bind(c, lambda e: self.after(AFTER, cmd(e)))
        self.canvas.bind(c, lambda e: self.after(AFTER, cmd(e)))

    def toggleLabels(self, e=None):
        if e and e.char != "l":
            return
        if self.labels:
            width0 = self.tree.column('#0')['width']
            self.tree.column('#0', width=width0 + self.col2_width)
            self.tree.column('#1', width=0)
            self.labels = False
        else:
            width0 = self.tree.column('#0')['width']
            self.tree.column('#0', width=width0 - self.col2_width)
            self.tree.column('#1', width=self.col2_width)
            self.labels = True

    def toggleFinished(self, e=None):
        if e and e.char != "x":
            return
        if loop.options['hide_finished']:
            loop.options['hide_finished'] = False
        else:
            loop.options['hide_finished'] = True
        logger.debug('reloading data')
        # self.updateAlerts()
        if self.weekly:
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.updateDay()
            self.showMonth()
        else:
            self.showView()

    def saveGeometry(self):
        str = self.geometry()
        fo = open(self.etmgeo, 'w')
        fo.write(str)
        fo.close()

    def restoreGeometry(self):
        if os.path.isfile(self.etmgeo):
            fo = open(self.etmgeo, "r")
            str = fo.read()
            fo.close()
            tup = [x.strip() for x in str.split(',')]
            if tup:
                self.geometry(tup[0])

    def closeItemMenu(self, event=None):
        if self.weekly or self.monthly:
            self.canvas.focus_set()
        else:
            self.tree.focus_set()
        self.itemmenu.unpost()

    def add2menu(self, parent, child):
        if child == (SEP, ):
            id = uuid.uuid1()
        elif len(child) > 1 and child[1]:
            id = uuid.uuid1()
            m = LASTLTR.search(child[1])
            if m:

                child = tuple(child)
        else:
            id = child[0]
        if len(child) >= 2:
            leaf = "{0}::{1}".format(child[0], child[1])
        else:
            leaf = "{0}::".format(child[0])
        self.menutree.create_node(leaf, id, parent=parent)

    def confirm(self, parent=None, title="", prompt="", instance="xyz"):
        ok, value = OptionsDialog(parent=parent, title=_("confirm").format(instance), prompt=prompt).getValue()
        return ok

    def selectCalendars(self):
        if self.default_calendars:
            prompt = _("Display items from calendars selected below.")
            title = CALENDARS
            if self.weekly or self.monthly:
                master = self.canvas
            else:
                master = self.tree

            values = OptionsDialog(parent=self, master=master, title=title, prompt=prompt, opts=loop.calendars, radio=False, yesno=False).getValue()

            if values != loop.calendars:
                loop.calendars = values
                loop.options['calendars'] = values
                data.setConfig(loop.options)
                self.updateCalendars()
        else:
            prompt = _("No calendars have been specified in etmtk.cfg.")
            self.textWindow(self, CALENDARS, prompt, opts=self.options)

    def updateCalendars(self, *args):
        cal_pattern = r'^%s' % '|'.join(
            [x[2] for x in loop.calendars if x[1]])
        self.cal_regex = re.compile(cal_pattern)
        self.update()
        self.updateAlerts()
        if self.weekly:
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.updateDay()
            self.showMonth()
        else:
            self.showView()

    def quit(self, e=None):
        ans = True
        if self.actionTimer.currentStatus == RUNNING:
            ans = self.confirm(
                title=_('Quit'),
                prompt=_("An action timer is running.\nDo you really want to quit?"),
                parent=self)
        else:
            ans = self.confirm(
                title=_('Quit'),
                prompt=_("Do you really want to quit?"),
                parent=self)
        if ans:
            self.actionTimer.pauseTimer()
            self.saveGeometry()
            self.destroy()

    def donothing(self, e=None):
        """For testing"""
        logger.debug('donothing')
        return "break"

    def openWithDefault(self, e=None):
        if not self.itemSelected or 'g' not in self.itemSelected:
            return(False)
        # path = self.itemSelected['g']
        path = expand_template(self.itemSelected['g'], self.itemSelected)

        if windoz:
            os.startfile(path)
            return()

        if mac:
            cmd = 'open' + " {0}".format(path)
        else:
            cmd = 'xdg-open' + " {0}".format(path)
        subprocess.call(cmd, shell=True)
        return

    def printWithDefault(self, s, e=None):
        fo = codecs.open(loop.tmpfile, 'w', loop.options['encoding']['file'])
        # add a trailing formfeed
        fo.write(s)
        fo.close()
        if windoz:
            os.startfile(loop.tmpfile, "print")
            return
        else:
            cmd = "lp -s -o media='letter' -o cpi=12 -o lpi=8 -o page-left=48 -o page-right=48 -o page-top=48 -o page-bottom=48 {0}\n".format(loop.tmpfile)
            # cmd = "lpr -l {0}".format(loop.tmpfile)
            subprocess.call(cmd, shell=True)
            return

    def showUserDetails(self, e=None):
        if not self.itemSelected or 'u' not in self.itemSelected:
            return
        if not loop.options['user_data']:
            return
        user = self.itemSelected['u']
        if user in loop.options['user_data']:
            detail = "\n".join(loop.options['user_data'][user])
        else:
            detail = _("No record was found for {0}".format(user))
        self.textWindow(self, user, detail, opts=loop.options)
        return

    def dateCalculator(self, event=None):
        prompt = """\
Enter an expression of the form "x [+-] y" where x is a date and y is
either a date or a time period if "-" is used and a time period if "+"
is used. Both x and y can be followed by timezones, e.g.,
     4/20 6:15p US/Central - 4/20 4:50p Asia/Shanghai
                       = 14h25m
     4/20 4:50p Asia/Shanghai + 14h25m US/Central
              = 2014-04-20 18:15-0500
The local timezone is used when none is given."""
        GetString(parent=self, title=_('date and time calculator'), prompt=prompt, opts=loop.options, process=date_calculator)
        return

    def availableDateCalculator(self, event=None):
        prompt = """\
Enter an expression of the form
    start; end; busy
where start and end are dates and busy is comma separated list of
busy dates or busy intervals, .e.g,
    6/1; 6/30; 6/2, 6/14-6/22, 6/5-6/9, 6/11-6/15, 6/17-6/29
returns:
    Sun Jun 01
    Tue Jun 03
    Wed Jun 04
    Tue Jun 10
    Mon Jun 30\
"""
        GetString(parent=self, title=_('available dates calculator'), prompt=prompt, opts={}, process=availableDates, font=self.tkfixedfont)
        return

    def exportToIcal(self, e=None):
        if self.itemSelected:
            self._exportItemToIcal()
        else:
            self._exportActiveToIcal()

    def _exportItemToIcal(self):
        if 'icsitem_file' in loop.options:
            res = export_ical_item(self.itemSelected, loop.options['icsitem_file'])
            if res:
                prompt = _("Selected item successfully exported to {0}".format(loop.options['icsitem_file']))
            else:
                prompt = _("Could not export selected item.")
        else:
            prompt = "icsitem_file is not set in etmtk.cfg"
        MessageWindow(self, 'Selected Item Export', prompt)

    def _exportActiveToIcal(self, event=None):
        if 'icscal_file' in loop.options:
            res = export_ical_active(loop.file2uuids, loop.uuid2hash, loop.options['icscal_file'], loop.calendars)
            if res:
                prompt = _("Active calendars successfully exported to {0}".format(loop.options['icscal_file']))
            else:
                prompt = _("Could not export active calendars.")
        else:
            prompt = "icscal_file is not set in etmtk.cfg"
        MessageWindow(self, 'Active Calendar Export', prompt)

    def newItem(self, e=None):
        # hack to avoid Ctrl-n activation
        if e and e.char != "n":
            return
        if self.weekly or self.monthly:
            master = self.canvas
        else:
            master = self.tree
        if self.view in [AGENDA] and self.active_date:
            if self.itemSelected:
                if 's' in self.itemSelected:
                    text = " @s {0}".format(self.active_date)
                elif 'c' in self.itemSelected:
                    text = " @c {0}".format(self.itemSelected['c'])
                else:
                    text = " "
            elif self.active_date:
                text = " @s {0}".format(self.active_date)
            changed = SimpleEditor(parent=self, master=master, start=text, options=loop.options).changed
        elif self.view in [DAY, WEEK, MONTH] and self.canvas_date:
            text = " @s {0}".format(self.canvas_date)
            changed = SimpleEditor(parent=self, master=master, start=text, options=loop.options).changed
        elif self.view in [KEYWORD, NOTE] and self.itemSelected:
            if self.itemSelected and 'k' in self.itemSelected:
                text = " @k {0}".format(self.itemSelected['k'])
            else:
                text = ""
            changed = SimpleEditor(parent=self, master=master, start=text, options=loop.options).changed
        elif self.view in [TAG]:
            if self.itemSelected and 't' in self.itemSelected:
                text = " @t {0}".format(", ".join(self.itemSelected['t']))
            else:
                text = ""
            changed = SimpleEditor(parent=self, master=master, start=text, options=loop.options).changed
        else:
            changed = SimpleEditor(parent=self, master=master, options=loop.options).changed
        if changed:
            logger.debug('changed, reloading data')
            loop.do_update = True
            self.updateAlerts()
            if self.weekly:
                self.updateDay()
                self.showWeek()
            elif self.monthly:
                self.updateDay()
                self.showMonth()
            else:
                self.showView()

    def which(self, act, instance="xyz"):
        prompt = "\n".join([
            _("You have selected an instance of a repeating"),
            _("item. What do you want to {0}?").format(act)])
        if act == DELETE:
            opt_lst = [
                _("this instance"),
                _("this and all subsequent instances"),
                _("all instances"),
                _("all previous instances")]
        else:
            opt_lst = [
                _("this instance"),
                _("this and all subsequent instances"),
                _("all instances")]

        if self.weekly or self.monthly:
            master = self.canvas
        else:
            master = self.tree
        index, value = OptionsDialog(parent=self, master=master, title=_("instance: {0}").format(instance), prompt=prompt, opts=opt_lst, yesno=False).getValue()
        return index, value

    def copyItem(self, e=None):
        """
        newhsh = selected, rephsh = None
        """
        if not self.itemSelected:
            return
        if e and e.char != 'c':
            return
        if 'r' in self.itemSelected:
            choice, value = self.which(COPY, self.dtSelected)
            logger.debug("{0}: {1}".format(choice, value))
            if not choice:
                self.tree.focus_set()
                return
            self.itemSelected['_dt'] = self.dtSelected
        else:
            ans = self.confirm(
                parent=self.tree,
                title=_('Confirm'),
                prompt=_("Open a copy of this item?"))
            if not ans:
                self.tree.focus_set()
                return
            choice = 3
        hsh_cpy = deepcopy(self.itemSelected)
        hsh_cpy['fileinfo'] = None

        title = _("new item")
        self.mode = 'new'
        if choice in [1, 2]:
            # we need to modify the copy according to the choice
            dt = hsh_cpy['_dt'].replace(
                tzinfo=tzlocal()).astimezone(gettz(hsh_cpy['z']))
            dtn = dt.replace(tzinfo=None)

            if choice == 1:
                # this instance
                for k in ['_r', 'o', '+', '-']:
                    if k in hsh_cpy:
                        del hsh_cpy[k]
                hsh_cpy['s'] = dtn

            elif choice == 2:
                # this and all subsequent instances
                if u'+' in hsh_cpy:
                    tmp_cpy = []
                    for d in hsh_cpy['+']:
                        if d >= dtn:
                            tmp_cpy.append(d)
                    hsh_cpy['+'] = tmp_cpy
                if u'-' in hsh_cpy:
                    tmp_cpy = []
                    for d in hsh_cpy['-']:
                        if d >= dtn:
                            tmp_cpy.append(d)
                    hsh_cpy['-'] = tmp_cpy
                hsh_cpy['s'] = dtn

        changed = SimpleEditor(parent=self, newhsh=hsh_cpy, rephsh=None, options=loop.options, title=title, modified=True).changed
        if changed:
            self.updateAlerts()
            if self.weekly:
                self.updateDay()
                self.showWeek()
            elif self.weekly:
                self.updateDay()
                self.showMonth()
            else:
                self.showView(row=self.topSelected)
        else:
            if self.weekly or self.monthly:
                self.canvas.focus_set()
            else:
                self.tree.focus_set()

    def deleteItem(self, e=None):
        if not self.itemSelected:
            return
        logger.debug('{0}: {1}'.format(self.itemSelected['_summary'], self.dtSelected))
        indx = 3
        if 'r' in self.itemSelected:
            indx, value = self.which(DELETE, self.dtSelected)
            logger.debug("{0}: {1}/{2}".format(self.dtSelected, indx, value))
            if not indx:
                if self.weekly or self.monthly:
                    self.canvas.focus_set()
                else:
                    self.tree.focus_set()
                return
            self.itemSelected['_dt'] = self.dtSelected
        else:
            ans = self.confirm(
                title=_('Confirm'),
                prompt=_("Delete this item?"),
                parent=self.tree)
            if not ans:
                self.tree.focus_set()
                return
        loop.item_hsh = self.itemSelected
        loop.cmd_do_delete(indx)

        self.updateAlerts()
        if self.weekly:
            self.canvas.focus_set()
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.canvas.focus_set()
            self.updateDay()
            self.showMonth()
        else:
            self.tree.focus_set()
            self.showView(row=self.topSelected)

    def moveItem(self, e=None):
        if not self.itemSelected:
            return
        if e and e.char != 'm':
            return
        logger.debug('{0}: {1}'.format(self.itemSelected['_summary'], self.dtSelected))
        oldrp, begline, endline = self.itemSelected['fileinfo']
        oldfile = os.path.join(loop.options['datadir'], oldrp)
        newfile = self.getDataFile(title="moving from {0}:".format(oldrp), start=oldfile)
        if not (newfile and os.path.isfile(newfile)):
            return
        if newfile == oldfile:
            return
        ret = loop.append_item(self.itemSelected, newfile)
        if ret != "break":
            # post message and quit
            prompt = _("""\
Adding item to {1} failed - aborted removing item from {2}""".format(
                newfile, oldfile))
            MessageWindow(self, 'Error', prompt)
            return
        loop.item_hsh = self.itemSelected
        ret = loop.delete_item()

        self.updateAlerts()
        if self.weekly:
            self.canvas.focus_set()
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.canvas.focus_set()
            self.updateDay()
            self.showWeek()
        else:
            self.tree.focus_set()
            self.showView(row=self.topSelected)

    def editItem(self, e=None):
        if not self.itemSelected:
            return
        logger.debug('starting editItem: {0}; {1}, {2}'.format(self.itemSelected['_summary'], self.dtSelected, type(self.dtSelected)))
        choice = 3
        title = ETM
        start_text = None
        filename = None
        if self.itemSelected['itemtype'] == '$':
            start_text = self.itemSelected['entry']
            hsh_rev = deepcopy(self.itemSelected)
        elif 'r' in self.itemSelected:
            # repeating
            choice, value = self.which(EDIT, self.dtSelected)
            logger.debug("{0}: {1}".format(choice, value))
            if self.weekly or self.monthly:
                self.canvas.focus_set()
            else:
                self.tree.focus_set()
            logger.debug(('dtSelected: {0}, {1}'.format(type(self.dtSelected), self.dtSelected)))
            self.itemSelected['_dt'] = self.dtSelected
            if not choice:
                self.tree.focus_set()
                return
        hsh_rev = hsh_cpy = None
        self.mode = 2  # replace
        if choice in [1, 2]:
            self.mode = 3  # new and replace - both newhsh and rephsh
            title = _("new item")
            hsh_cpy = deepcopy(self.itemSelected)
            hsh_rev = deepcopy(self.itemSelected)
            # we will be editing and adding hsh_cpy and replacing hsh_rev

            dt = hsh_cpy['_dt'].replace(
                tzinfo=tzlocal()).astimezone(gettz(hsh_cpy['z']))
            dtn = dt.replace(tzinfo=None)

            if choice == 1:
                # this instance
                if '+' in hsh_rev and dtn in hsh_rev['+']:
                    hsh_rev['+'].remove(dtn)
                    if not hsh_rev['+'] and hsh_rev['r'] == 'l':
                        del hsh_rev['r']
                        del hsh_rev['_r']
                else:
                    hsh_rev.setdefault('-', []).append(dt)
                for k in ['_r', 'o', '+', '-']:
                    if k in hsh_cpy:
                        del hsh_cpy[k]
                hsh_cpy['s'] = dtn

            elif choice == 2:
                # this and all subsequent instances
                tmp = []
                for h in hsh_rev['_r']:
                    if 'f' in h and h['f'] != u'l':
                        h['u'] = dt - ONEMINUTE
                    tmp.append(h)
                hsh_rev['_r'] = tmp
                if u'+' in hsh_rev:
                    tmp_rev = []
                    tmp_cpy = []
                    for d in hsh_rev['+']:
                        if d < dtn:
                            tmp_rev.append(d)
                        else:
                            tmp_cpy.append(d)
                    hsh_rev['+'] = tmp_rev
                    hsh_cpy['+'] = tmp_cpy
                if u'-' in hsh_rev:
                    tmp_rev = []
                    tmp_cpy = []
                    for d in hsh_rev['-']:
                        if d < dtn:
                            tmp_rev.append(d)
                        else:
                            tmp_cpy.append(d)
                    hsh_rev['-'] = tmp_rev
                    hsh_cpy['-'] = tmp_cpy
                hsh_cpy['s'] = dtn
        else:  # replace
            self.mode = 2
            hsh_rev = deepcopy(self.itemSelected)

        logger.debug("mode: {0}; newhsh: {1}; rephsh: {2}".format(self.mode, hsh_cpy is not None, hsh_rev is not None))
        changed = SimpleEditor(parent=self, file=filename, newhsh=hsh_cpy, rephsh=hsh_rev, options=loop.options, title=title, start=start_text).changed

        if changed:
            logger.debug("starting if changed")
            loop.do_update = True
            self.updateAlerts()
            if self.weekly:
                self.canvas.focus_set()
                self.updateDay()
                self.showWeek()
            elif self.monthly:
                self.canvas.focus_set()
                self.updateDay()
                self.showMonth()
            else:
                self.tree.focus_set()
                self.showView(row=self.topSelected)
                self.update_idletasks()
            logger.debug("leaving if changed")

        else:
            if self.weekly or self.monthly:
                self.canvas.focus_set()
            else:
                self.tree.focus_set()
        logger.debug('ending editItem')
        return

    def editItemFile(self, e=None):
        if not self.itemSelected:
            return
        logger.debug('starting editItemFile: {0}; {1}, {2}, {3}'.format(self.itemSelected['_summary'], self.dtSelected, type(self.dtSelected), self.itemSelected['fileinfo']))
        self.editFile(e, file=os.path.join(loop.options['datadir'], self.itemSelected['fileinfo'][0]), line=self.itemSelected['fileinfo'][1])

    def editFile(self, e=None, file=None, line=None, config=False):
        if e and e.char and e.char not in ["F", "E", "C", "S", "P"]:
            logger.debug('e.char: "{0}"'.format(e.char))
            return
        titlefile = os.path.normpath(relpath(file, loop.options['datadir']))
        logger.debug('file: {0}; config: {1}'.format(file, config))
        if self.weekly or self.monthly:
            master = self.canvas
        else:
            master = self.tree
        changed = SimpleEditor(parent=self, master=master, file=file, line=line, options=loop.options, title=titlefile).changed
        logger.debug('changed: {0}'.format(changed))
        if changed:
            logger.debug("config: {0}".format(config))
            if config:
                current_options = deepcopy(loop.options)
                (user_options, options, use_locale) = data.get_options(
                    d=loop.options['etmdir'])
                loop.options = options
                if options['calendars'] != current_options['calendars']:
                    self.updateCalendars()
            loop.do_update = True

            logger.debug("changed - calling updateAlerts")
            self.updateAlerts()
            if self.weekly:
                self.canvas.focus_set()
                self.updateDay()
                self.showWeek()
            elif self.monthly:
                self.canvas.focus_set()
                self.updateDay()
                self.showMonth()
            else:
                self.tree.focus_set()
                self.showView()
        return changed

    def editConfig(self, e=None):
        file = loop.options['config']
        self.editFile(e, file=file, config=True)

    def editCfgFiles(self, e=None):
        other = []
        if 'cfg_files' in loop.options:
            for key in ['completions', 'reports', 'users']:
                other.extend(loop.options['cfg_files'][key])
        prefix, tuples = getFileTuples(loop.options['datadir'], include=r'*.cfg', other=other)
        ret = FileChoice(self, "open configuration file", prefix=prefix, list=tuples).returnValue()
        if not (ret and os.path.isfile(ret)):
            return False
        self.editFile(e, file=ret, config=True)

    def getReportsFile(self, e=None):
        ret = FileChoice(self, "append to reports file", prefix=self.loop.options['etmdir'], list=self.loop.options['report_files']).returnValue()
        if not (ret and os.path.isfile(ret)):
            return False
        return ret

    def getDataFile(self, e=None, title="data file", start=''):
        prefix, tuples = getFileTuples(loop.options['datadir'], include=r'*.txt')
        ret = FileChoice(self, title, prefix=prefix, list=tuples, start=start).returnValue()
        if not (ret and os.path.isfile(ret)):
            return False
        return ret

    def editScratch(self, e=None):
        file = loop.options['scratchpad']
        self.editFile(e, file=file)

    def editData(self, e=None):
        if e and e.char != "F":
            return
        prefix, tuples = getFileTuples(loop.options['datadir'], include=r'*.txt', all=False)
        ret = FileChoice(self, "open data file", prefix=prefix, list=tuples).returnValue()
        if not (ret and os.path.isfile(ret)):
            return False
        self.editFile(e, file=ret)

    def newFile(self, e=None):
        if e and e.char != "N":
            return
        other = [os.path.join(loop.options['etmdir'], 'etmtk.cfg')]
        if 'cfg_files' in loop.options:
            for key in ['completions', 'reports', 'users']:
                other.extend(loop.options['cfg_files'][key])
        prefix, tuples = getFileTuples(loop.options['datadir'], include=r'*', other=other, all=True)
        filename = FileChoice(self, "create new file", prefix=prefix, list=tuples, new=True).returnValue()
        if not filename:
            return
        if os.path.isfile(filename):
            prompt = _("Aborting. File {0} already exists.").format(filename)
            MessageWindow(self, title=_("new file"), prompt=prompt)
        else:
            pth = os.path.split(filename)[0]
            if not os.path.isdir(pth):
                os.makedirs(pth)
            fo = codecs.open(filename, 'w', loop.options['encoding']['file'])
            fo.write("")
            fo.close()
            prompt = _('created: {0}').format(filename)
            if self.weekly or self.monthly:
                p = self.canvas
            else:
                p = self.tree
            ans = self.confirm(
                parent=p,
                title=_('etm'),
                prompt=_("file: {0}\nhas been created.\nOpen it for editing?").format(filename))
            if ans:
                self.editFile(None, filename)

    def finishItem(self, e=None):
        if e and e.char != "f":
            return
        if not (self.itemSelected and self.itemSelected['itemtype'] in ['-', '+', '%']):
            return
        prompt = _("""\
Enter the completion date for the item or return an empty string to
use the current date. Relative dates and fuzzy parsing are supported.""")
        d = GetDateTime(parent=self, title=_('date'), prompt=prompt)
        chosen_day = d.value
        if chosen_day is None:
            return ()
        logger.debug('completion date: {0}'.format(chosen_day))
        loop.item_hsh = self.itemSelected
        loop.cmd_do_finish(chosen_day, options=loop.options)

        self.updateAlerts()
        if self.weekly:
            self.canvas.focus_set()
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.canvas.focus_set()
            self.updateDay()
            self.showMonth()
        else:
            self.tree.focus_set()
            self.showView(row=self.topSelected)

    def rescheduleItem(self, e=None):
        if e and e.char != 'r':
            return
        if not self.itemSelected:
            return
        loop.item_hsh = self.itemSelected
        if self.dtSelected:
            loop.old_dt = old_dt = self.dtSelected
            title = _('rescheduling {0}').format(old_dt.strftime(
                rrulefmt))
        else:
            loop.old_dt = None
            title = _('scheduling an undated item')
        logger.debug('dtSelected: {0}'.format(self.dtSelected))
        prompt = _("""\
Enter the new date and time for the item or return an empty string to
use the current time. Relative dates and fuzzy parsing are supported.""")
        dt = GetDateTime(parent=self, title=title, prompt=prompt)
        new_dt = dt.value
        if new_dt is None:
            return
        new_dtn = new_dt.astimezone(gettz(self.itemSelected['z'])).replace(tzinfo=None)
        logger.debug('rescheduled from {0} to {1}'.format(self.dtSelected, new_dtn))
        loop.cmd_do_reschedule(new_dtn)

        self.updateAlerts()
        if self.weekly:
            self.canvas.focus_set()
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.canvas.focus_set()
            self.updateDay()
            self.showMonthly()
        else:
            self.tree.focus_set()
            self.showView(row=self.topSelected)

    def scheduleNewItem(self, e=None):
        if e and e.char != 'i':
            return
        if not self.itemSelected:
            return
        loop.item_hsh = self.itemSelected
        if self.dtSelected:
            loop.old_dt = self.dtSelected
            title = _('adding new instance')
        else:
            loop.old_dt = None
            title = _('scheduling an undated item')
        logger.debug('dtSelected: {0}'.format(self.dtSelected))
        prompt = _("""\
Enter the new date and time for the item or return an empty string to
use the current time. Relative dates and fuzzy parsing are supported.""")
        dt = GetDateTime(parent=self, title=title, prompt=prompt)
        new_dt = dt.value
        if new_dt is None:
            return
        new_dtn = new_dt.astimezone(gettz(self.itemSelected['z'])).replace(tzinfo=None)
        logger.debug('scheduled new instance: {0}'.format(new_dtn))
        loop.cmd_do_schedulenew(new_dtn)

        self.updateAlerts()
        if self.weekly:
            self.canvas.focus_set()
            self.updateDay()
            self.showWeek()
        elif self.monthly:
            self.canvas.focus_set()
            self.updateDay()
            self.showMonth()
        else:
            self.tree.focus_set()
            self.showView(row=self.topSelected)

    def showAlerts(self, e=None):
        # hack to avoid activating with Ctrl-a
        if e and e.char != "a":
            return
        t = _('remaining alerts for today')
        header = "{0:^7}\t{1:^7}\t{2:<8}{3:<26}".format(
            _('alert'),
            _('event'),
            _('type'),
            _('summary'))
        divider = '-' * 52
        if self.activeAlerts:

            s = '%s\n%s\n%s' % (
                header, divider, "\n".join(
                    ["{0:^7}\t{1:^7}\t{2:<8}{3:<26}".format(
                        x[2]['alert_time'], x[2]['_event_time'],
                        ", ".join(x[2]['_alert_action']),
                        utf8(x[2]['summary'][:26])) for x in self.activeAlerts]))
        else:
            s = _("None                                 ")
        self.textWindow(self, t, s, opts=self.options)

    def agendaView(self, e=None):
        self.setView(AGENDA)

    def dayView(self, e=None):
        self.setView(DAY)

    def pathView(self, e=None):
        self.setView(PATH)

    def keywordView(self, e=None):
        self.setView(KEYWORD)

    def tagView(self, e=None):
        self.setView(TAG)

    def customView(self, e=None):
        # TODO: finish this
        self.content.delete("1.0", END)
        # self.fltr.forget()
        self.clearTree()
        self.setView(CUSTOM)
        pass

    def noteView(self, e=None):
        self.setView(NOTE)

    def updateDay(self, e=None):
        self.mode = "command"
        self.process_input(event=e, cmd='d')

    def setView(self, view, row=None):
        self.rowSelected = None
        if view in [DAY, WEEK, MONTH]:
            self.toolsmenu.entryconfig(1, state="normal")
        else:
            self.toolsmenu.entryconfig(1, state="disabled")
        if self.weekly and view not in [DAY, WEEK]:
            self.closeWeekly()
        if self.monthly and view not in [DAY, MONTH]:
            self.closeMonthly()
        if view == CUSTOM:
            logger.debug('showing custom_box')
            self.fltr.forget()
            self.custom_box.pack(side="left", fill=X, padx=0, expand=1)
            self.custom_box.focus_set()
            for i in range(len(self.rm_options)):
                self.custommenu.entryconfig(i, state="normal")
        else:
            if self.view == CUSTOM:
                # we're leaving custom view
                logger.debug('removing custom_box')
                self.custom_box.forget()
                self.fltr.pack(side="left", padx=0, expand=1, fill=X)
                for i in range(len(self.rm_options)):
                    self.custommenu.entryconfig(i, state="disabled")
                self.saveSpecs()
        self.view = view
        logger.debug("setView view: {0}. Calling showView.".format(view))
        self.showView(row=row)

    def filterView(self, e, *args):
        self.depth2id = {}
        fltr = self.filterValue.get()
        cn = self.vm_options[self.vm_opts.index(self.view)][1]
        if cn in ['w', 'm']:
            # with week or month views use the day view command
            cn = 'd'
        cmd = "{0} {1}".format(cn, fltr)
        self.mode = 'command'
        self.process_input(event=e, cmd=cmd)

    def showView(self, e=None, row=None):
        tt = TimeIt(loglevel=2, label="{0} view".format(self.view))
        self.depth2id = {}
        self.currentView.set(self.view)
        fltr = self.filterValue.get()
        if self.view != CUSTOM:
            cmd = "{0} {1}".format(
                self.vm_options[self.vm_opts.index(self.view)][1], fltr)
            self.mode = 'command'
            self.process_input(event=e, cmd=cmd)
            if row:
                row = max(0, row - 1)
                self.tree.yview(row)
        tt.stop()

    def showBusyPeriods(self, e=None):
        if e and e.char != "b":
            return
        if self.busy_info is None:
            return()
        theweek, weekdays, busy_lst, occasion_lst = self.busy_info
        theweek = _("Busy periods in {0}").format(theweek)

        lines = [theweek, '-' * len(theweek)]
        ampm = loop.options['ampm']
        s1 = s2 = ''
        for i in range(len(busy_lst)):
            times = []
            for tup in busy_lst[i]:
                t1 = max(7 * 60, tup[0])
                t2 = min(23 * 60, max(420, tup[1]))
                if t1 != t2:
                    t1h, t1m = (t1 // 60, t1 % 60)
                    t2h, t2m = (t2 // 60, t2 % 60)
                    if ampm:
                        if t1h == 12:
                            s1 = 'pm'
                        elif t1h > 12:
                            t1h -= 12
                            s1 = 'pm'
                        else:
                            s1 = 'am'
                        if t2h == 12:
                            s2 = 'pm'
                        elif t2h > 12:
                            t2h -= 12
                            s2 = 'pm'
                        else:
                            s2 = 'am'

                    T1 = "%d:%02d%s" % (t1h, t1m, s1)
                    T2 = "%d:%02d%s" % (t2h, t2m, s2)

                    times.append("%s-%s" % (T1, T2))
            if times:
                lines.append("%s: %s" % (weekdays[i], "; ".join(times)))
        s = "\n".join(lines)
        self.textWindow(parent=self, title=_('busy times'), prompt=s, opts=self.options)

    def showFreePeriods(self, e=None):
        if e and e.char != 'f':
            return
        if self.busy_info is None or 'freetimes' not in loop.options:
            return()
        ampm = loop.options['ampm']
        om = loop.options['freetimes']['opening']
        cm = loop.options['freetimes']['closing']
        mm = loop.options['freetimes']['minimum']
        bm = loop.options['freetimes']['buffer']
        prompt = _("""\
Enter the shortest time period you want displayed in minutes.""")
        mm = GetInteger(parent=self, title=_("depth"), prompt=prompt, opts=[0], default=mm).value
        if mm is None:
            self.canvas.focus_set()
            return
        theweek, weekdays, busy_lst, occasion_lst = self.busy_info
        theweek = _("Free periods in {0}").format(theweek)
        lines = [theweek, '-' * len(theweek)]
        s1 = s2 = ''
        for i in range(len(busy_lst)):
            times = []
            busy = []
            for tup in busy_lst[i]:
                t1 = max(om, tup[0] - bm)
                t2 = min(cm, max(om, tup[1]) + bm)
                if t2 > t1:
                    busy.append((t1, t2))
            lastend = om
            free = []
            for tup in busy:
                if tup[0] - lastend >= mm:
                    free.append((lastend, tup[0]))
                lastend = tup[1]
            if cm - lastend >= mm:
                free.append((lastend, cm))
            for tup in free:
                t1, t2 = tup
                if t1 != t2:
                    t1h, t1m = (t1 // 60, t1 % 60)
                    t2h, t2m = (t2 // 60, t2 % 60)
                    if ampm:
                        if t1h == 12:
                            s1 = 'pm'
                        elif t1h > 12:
                            t1h -= 12
                            s1 = 'pm'
                        else:
                            s1 = 'am'
                        if t2h == 12:
                            s2 = 'pm'
                        elif t2h > 12:
                            t2h -= 12
                            s2 = 'pm'
                        else:
                            s2 = 'am'
                    T1 = "%d:%02d%s" % (t1h, t1m, s1)
                    T2 = "%d:%02d%s" % (t2h, t2m, s2)
                    times.append("%s-%s" % (T1, T2))
            if times:
                lines.append("%s: %s" % (weekdays[i], "; ".join(times)))
        lines.append('-' * len(theweek))
        lines.append("Only periods of at least {0} minutes are displayed.".format(mm))
        s = "\n".join(lines)
        self.textWindow(parent=self, title=_('free times'), prompt=s, opts=self.options)

    def getWeek(self, chosen_day=None):
        if chosen_day is None:
            chosen_day = get_current_time()
        yn, wn, dn = chosen_day.isocalendar()
        self.prev_week = chosen_day - 7 * ONEDAY
        self.next_week = chosen_day + 7 * ONEDAY
        self.curr_week = chosen_day
        if dn > 1:
            days = dn - 1
        else:
            days = 0
        self.week_beg = weekbeg = chosen_day - days * ONEDAY
        logger.debug('week_beg: {0}'.format(self.week_beg))
        weekend = chosen_day + (6 - days) * ONEDAY
        weekdays = []
        weekdates = []

        day = weekbeg
        self.active_date = weekbeg.date()
        busy_lst = []
        occasion_lst = []
        matching = self.cal_regex is not None and self.default_regex is not None
        while day <= weekend:
            weekdays.append(s2or3(day.strftime("%a")))
            weekdates.append(leadingzero.sub("", day.strftime("%d")))
            isokey = day.isocalendar()

            day = day + ONEDAY

        ybeg = weekbeg.year
        yend = weekend.year
        mbeg = weekbeg.month
        mend = weekend.month
        # busy_lst: list of days 0 (monday) - 6 (sunday) where each day is a list of (start minute, end minute, id, summary-time str and file info) tuples

        if mbeg == mend:
            header = "{0} - {1}".format(
                fmt_dt(weekbeg, '%b %d'), fmt_dt(weekend, '%d, %Y'))
        elif ybeg == yend:
            header = "{0} - {1}".format(
                fmt_dt(weekbeg, '%b %d'), fmt_dt(weekend, '%b %d, %Y'))
        else:
            header = "{0} - {1}".format(
                fmt_dt(weekbeg, '%b %d, %Y'), fmt_dt(weekend, '%b %d, %Y'))
        header = leadingzero.sub('', header)
        theweek = _("{0} {1}: {2}").format(_("Week"), wn, header)
        return theweek, weekdays, weekdates


    def closeWeekly(self, event=None):
        self.week_height = self.topwindow.panecget(self.toppane, "height")
        self.topwindow.forget(self.toppane)
        self.weekly = False
        self.tree.pack(fill="both", expand=1, padx=4, pady=0)
        self.update_idletasks()
        for i in range(4, 6):
            self.toolsmenu.entryconfig(i, state="disabled")

        self.bind("<Control-f>", self.setFilter)

    def showWeekly(self, event=None, chosen_day=None):
        """
        Open the canvas at the current week
        """
        self.custom_box.forget()
        tt = TimeIt(loglevel=2, label="week view")
        logger.debug("chosen_day: {0}; active_date: {1}".format(chosen_day, self.active_date))
        if self.weekly:
            # we're in weekview already
            return
        if self.monthly:
            self.closeMonthly()
        self.content.delete("1.0", END)
        self.weekly = True

        self.setView(DAY)

        self.view = WEEK

        if chosen_day is not None:
            self.chosen_day = chosen_day
        elif self.active_date:
            self.chosen_day = datetime.combine(self.active_date, time())
        else:
            self.chosen_day = get_current_time()

        self.topwindow.add(self.toppane, padx=0, pady=0, before=self.botwindow, height=self.week_height)

        if self.options['ampm']:
            self.hours = ["{0}am".format(i) for i in range(7, 12)] + ['12pm'] + ["{0}pm".format(i) for i in range(1, 12)]
        else:
            self.hours = ["{0}:00".format(i) for i in range(7, 24)]
        for i in range(4, 6):
            self.toolsmenu.entryconfig(i, state="normal")
        self.showWeek(event=event, week=0)
        tt.stop()

    def priorWeekMonth(self, event=None):
        if self.weekly:
            self.showWeek(event, week=-1)
        elif self.monthly:
            self.showMonth(event, month=-1)

    def nextWeekMonth(self, event=None):
        if self.weekly:
            self.showWeek(event=event, week=1)
        elif self.monthly:
            self.showMonth(event=event, month=1)

    def showWeek(self, event=None, week=None):
        self.canvas.focus_set()
        self.selectedId = None

        matching = self.cal_regex is not None and self.default_regex is not None

        busy_dates = []

        self.current_day = get_current_time().replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
        logger.debug('self.current_day: {0}, minutes: {1}'.format(self.current_day, self.current_minutes))
        self.x_win = self.toppane.winfo_width()
        # self.y_win = self.canvas.winfo_height()
        self.y_win = self.toppane.winfo_height()
        logger.debug("win: {0}, {1}".format(self.x_win, self.y_win))
        logger.debug("event: {0}, week: {1}, chosen_day: {2}".format(event, week, self.chosen_day))
        use_chosen = False
        if week in [-1, 0, 1]:
            if week == 0:
                day = get_current_time()
            elif week == 1:
                day = self.next_week
            elif week == -1:
                day = self.prev_week
            self.chosen_day = day
        elif self.chosen_day:
            use_chosen = True
            self.year_month = [self.chosen_day.year, self.chosen_day.month]
            day = self.chosen_day
        else:
            return
        logger.debug('week active_date: {0}'.format(self.active_date))
        theweek, weekdays, weekdates = self.getWeek(day)
        busy_lst = []
        occasion_lst = []
        weekdaynum = day.isocalendar()[2]
        # reset day to Monday of the current week
        day = day - (weekdaynum - 1) * ONEDAY
        if use_chosen:
            scrolldate = self.chosen_day.date()
            self.canvas_idpos = weekdaynum - 1
        else:
            scrolldate = day.date()
            self.canvas_idpos = 0
        self.scrollToDate(scrolldate)
        self.OnSelect()
        self.canvas.delete("all")
        l = 4
        r = 4
        t = 22
        b = 4
        if event:
            logger.debug('event: {0}'.format(event))
            w, h = event.width, event.height
            if type(w) is int and type(h) is int:
                self.canvas_width = w
                self.canvas_height = h
            else:
                w = self.canvas.winfo_width()
                h = self.canvas.winfo_height()
        else:
            w = self.canvas.winfo_width()
            h = self.week_height
        logger.debug("w: {0} {1}; h: {2} {3}, l: {4} {5}, t: {6} {7}".format(w, type(w), h, type(h), l, type(l), t, type(t)))
        self.margins = (w, h, l, r, t, b)
        self.week_x = x_ = Decimal(w - 1 - l - r) / Decimal(7)
        self.week_y = y_ = Decimal(h - 1 - t - b)
        logger.debug("x: {0}, y: {1}".format(x_, y_))

        # week
        self.currentView.set(theweek)
        self.busyHsh = {}

        # occasions
        busy_ids = set([])
        monthid2date = {}

        # self.canvas.bind('<Escape>', self.on_leave_item)

        # weekdays
        intervals = [360, 720, 1080, 1440]
        busywidth = 2
        offset = 6
        indent = 7

        barcolor = "SteelBlue3"
        nightcolor = barcolor
        morningcolor = barcolor
        afternooncolor = barcolor
        eveningcolor = barcolor

        conf_ids = []
        self.today_id = None
        self.timeline = None
        self.last_minutes = None

        # x_ = x
        # y_ = y

        for i in range(7):
            fill = "SteelBlue4"
            flagcolor = "white"
            busytimes = 0
            start_x = l + i * x_
            end_x = start_x + x_
            start_y = int(t)
            end_y = start_y + y_
            xy = int(start_x), int(start_y), int(end_x), int(end_y)
            p = int(l + x_ / 2 + x_ * i), int(t + y_ / 2)
            tl_x = bl_x = int(l + x_ * i)
            tl_y = tr_y = int(t)
            tr_x = br_x = int(tl_x + x_)
            bl_y = br_y = int(tl_y + y_)
            w_ = x_ - 12
            h_ = y_ - 12

            thisdate = (day + i * ONEDAY).date()
            isokey = thisdate.isocalendar()
            tags = []
            id = self.canvas.create_rectangle(xy, outline="", width=0)
            busy_ids.add(id)
            monthid2date[id] = thisdate
            today = (thisdate == self.current_day.date())
            if today:
                flagcolor = CURRENTFILL
                tags.append('current_day')
            if loop.occasions is not None and isokey in loop.occasions:
                bt = []
                for item in loop.occasions[isokey]:
                    it = list(item)
                    if matching:
                        if not self.cal_regex.match(it[-1]):
                            continue
                        mtch = (self.default_regex.match(it[-1]) is not None)
                    else:
                        mtch = True
                    it.append(mtch)
                    item = tuple(it)
                    bt.append(item)
                occasion_lst.append(bt)
                if bt:
                    if not today:
                        flagcolor = OCCASIONFILL
                        tags.append('occasion')
                    self.busyHsh.setdefault(id, []).extend(["^ {0}".format(x[0]) for x in bt])
            else:
                occasion_lst.append([])

            if loop.busytimes is not None and isokey in loop.busytimes:
                bt = []
                overlap = False
                for item in loop.busytimes[isokey]:
                    it = list(item)
                    if it[0] == it[1]:
                        # skip reminders
                        continue
                    if matching:
                        if not self.cal_regex.match(it[-1]):
                            continue
                        mtch = (self.default_regex.match(it[-1]) is not None)
                    else:
                        mtch = True
                    it.append(mtch)
                    item = tuple(it)
                    bt.append(item)
                busy_lst.append(bt)
                busy_dates.append(thisdate.strftime("%a %d"))
                if bt:
                    lastend = 0
                    for pts in bt:
                        busytimes += pts[1] - pts[0]
                        self.busyHsh.setdefault(id, []).append("* {0}".format(pts[2]))
                        if pts[0] < lastend:
                            overlap = True
                        lastend = pts[1]
                    if overlap:
                        flagcolor = "red"
                    tags.append('busy')

                    busylines = [[], [], [], []]
                    # each side 360 minutes plus 2 times bar width

                    for pts in bt:
                        pt1 = max(0, pts[0])
                        pt2 = min(pts[1], 1440)
                        tmp = [[], [], [], []]

                        for ii in range(0, 4):
                            if pt1 >= intervals[ii]:
                                continue
                            tmp[ii].append(pt1)
                            for jj in range(ii, 4):
                                if jj > ii:
                                    tmp[jj].append(intervals[jj-1])
                                if pt2 <= intervals[jj]:
                                    tmp[jj].append(pt2)
                                    break
                                else:
                                    tmp[jj].append(intervals[jj])
                            break
                        for ii in range(4):
                            if tmp[ii]:
                                busylines[ii].append(tmp[ii])


                    if busylines:
                        for side in range(4):
                            lines = busylines[side]
                            if lines:
                                if side == 0: # left
                                    for line in lines:
                                        bx = ex = bl_x + offset
                                        by = bl_y - indent - int(Decimal((line[0])/360) * h_)
                                        ey = bl_y - indent - int(Decimal((line[1])/360) * h_)
                                        self.canvas.create_line((bx, by, ex, ey), fill=nightcolor, width=busywidth, tag="busy")
                                elif side == 1: # top
                                    for line in lines:
                                        by = ey = tl_y + offset
                                        bx = tl_x + indent + int(Decimal((line[0]-360)/360) * w_)
                                        ex = tl_x + indent + int(Decimal((line[1]-360)/360) * w_)
                                        self.canvas.create_line((bx, by, ex, ey), fill=morningcolor, width=busywidth, tag="busy")
                                elif side == 2: # right
                                    for line in lines:
                                        bx = ex = tr_x - offset
                                        by = tr_y + indent + int(Decimal((line[0]-720)/360) * h_)
                                        ey = tr_y + indent + int(Decimal((line[1]-720)/360) * h_)
                                        self.canvas.create_line((bx, by, ex, ey), fill=afternooncolor, width=busywidth, tag="busy")
                                elif side == 3: # bottom
                                    for line in lines:
                                        by = ey = br_y - offset
                                        bx = br_x - indent - int(Decimal((line[0]-1080)/360) * w_)
                                        ex = br_x - indent - int(Decimal((line[1]-1080)/360) * w_)
                                        self.canvas.create_line((bx, by, ex, ey), fill=eveningcolor, width=busywidth, tag="busy")

                        bx = bl_x + offset - 1.5 * busywidth
                        ex = bl_x + offset + .5 * busywidth
                        by = bl_y - indent + 1.5 * busywidth
                        ey = bl_y - indent - .5 * busywidth
                        if flagcolor:
                            self.canvas.create_rectangle((bx, by, ex, ey), fill=flagcolor, outline=flagcolor, tag="busy")
            else:
                busy_lst.append([])
                busy_dates.append(thisdate.strftime("%a %d"))

            if 'current_day' in tags:
                self.canvas.itemconfig(id, tag='current_day', fill=CURRENTFILL)
            elif 'occasion' in tags:
                self.canvas.itemconfig(id, tag='occasion', fill=OCCASIONFILL)
            elif 'busy' in tags:
                self.canvas.itemconfig(id, tag='busy', fill="white")

            # if fill:
            self.canvas.create_text(p, text="{0}".format(weekdates[i]), fill=barcolor)

        busy_ids = list(busy_ids)

        self.conf_ids = conf_ids


        # border
        # xy = int(l), int(t), int(l + x_ * 7), int(t + y_)
        # self.canvas.create_rectangle(xy, tag="grid")

        # verticals
        for i in range(1, 7):
            xy = int(l + x_ * i), int(t-18), int(l + x_ * i), int(t + y_)
            self.canvas.create_line(xy, fill=LINECOLOR, tag="grid")

        for i in range(7):
            p = int(l + x_ / 2 + x_ * i), int(t - 10)
            self.canvas.create_text(p, text="{0}".format(weekdays[i]), fill=barcolor)

        self.busy_info = (theweek, busy_dates, busy_lst, occasion_lst)
        self.busy_ids = busy_ids
        self.busy_ids.sort()
        for id in self.busy_ids:
            self.canvas.tag_bind(id, '<Any-Enter>', self.on_enter_item)
            # self.canvas.tag_bind(id, '<Any-Leave>', self.on_leave_item)
        self.canvas_ids = self.busy_ids
        self.monthid2date = monthid2date


    def closeMonthly(self, event=None):
        self.month_height = self.topwindow.panecget(self.toppane, "height")
        self.topwindow.forget(self.toppane)
        self.monthly = False
        self.tree.pack(fill="both", expand=1, padx=4, pady=0)
        self.update_idletasks()
        for i in range(4, 6):
            self.toolsmenu.entryconfig(i, state="disabled")
        self.bind("<Control-f>", self.setFilter)

    def showMonthly(self, event=None, chosen_day=None):
        """
        Open the canvas at the current week
        """
        self.custom_box.forget()
        tt = TimeIt(loglevel=2, label="month view")
        logger.debug("chosen_day: {0}; active_date: {1}".format(chosen_day, self.active_date))
        if self.monthly:
            # we're in month view already
            return
        if self.weekly:
            self.closeWeekly()
        self.content.delete("1.0", END)
        self.monthly = True
        for i in range(4, 6):
            self.toolsmenu.entryconfig(i, state="normal")

        self.setView(DAY)

        self.view = MONTH
        self.currentView.set(MONTH)

        if chosen_day is not None:
            self.chosen_day = chosen_day
        elif self.active_date:
            self.chosen_day = datetime.combine(self.active_date, time())
        else:
            self.chosen_day = get_current_time()

        self.topwindow.add(self.toppane, padx=0, pady=0, before=self.botwindow, height=self.month_height)

        self.showMonth(event=event)
        tt.stop()

    def showMonth(self, event=None, month=None):
        self.canvas.focus_set()
        self.selectedId = None
        matching = self.cal_regex is not None and self.default_regex is not None
        busy_lst = []
        busy_dates = []
        occasion_lst = []

        self.current_day = get_current_time().replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)

        logger.debug('self.current_day: {0}, minutes: {1}'.format(self.current_day, self.current_minutes))
        self.x_win = self.canvas.winfo_width()
        self.y_win = self.canvas.winfo_height()
        month_day = 1
        use_chosen = False
        if month in [-1, 0, 1]:
            if month == 0:
                self.year_month = [self.current_day.year, self.current_day.month]
            elif month == 1:
                self.year_month[1] += 1
                if self.year_month[1] > 12:
                    self.year_month[1] -= 12
                    self.year_month[0] += 1
            elif month == -1:
                self.year_month[1] -= 1
                if self.year_month[1] < 1:
                    self.year_month[1] += 12
                    self.year_month[0] -= 1
        elif self.chosen_day:
            use_chosen = True
            self.year_month = [self.chosen_day.year, self.chosen_day.month]
            month_day = self.chosen_day.day
        else:
            return
        logger.debug('month active_date: {0}'.format(self.active_date))
        day = date(self.year_month[0], self.year_month[1], month_day)
        if use_chosen:
            scrolldate = self.chosen_day.date()
            self.canvas_idpos = month_day - 1
            # self.canvas_idpos = weekdaynum - 1
        else:
            scrolldate = day
            self.canvas_idpos = 0
        self.scrollToDate(scrolldate)

        weeks = self.monthly_calendar.monthdatescalendar(*self.year_month)
        num_weeks = len(weeks)
        weekdays = [s2or3(x.strftime("%a")) for x in weeks[0]]
        themonth = weeks[1][0].strftime("%B %Y")
        self.canvas.delete("all")
        l = 4
        r = 4
        t = 22
        b = 4
        if event:
            logger.debug('event: {0}'.format(event))
            w, h = event.width, event.height
            if type(w) is int and type(h) is int:
                self.canvas_width = w
                self.canvas_height = h
            else:
                w = self.canvas.winfo_width()
                h = self.canvas.winfo_height()
        else:
            w = self.canvas.winfo_width()
            # h = self.canvas.winfo_height()
            h = self.month_height
        logger.debug("w: {0}, h: {1}, l: {2}, t: {3}".format(w, h, l, t))

        self.margins = (w, h, l, r, t, b)

        self.month_x = x_ = Decimal(w - 1 - l - r) / Decimal(7)
        self.month_y = y_ = Decimal(h - 1 - t - b) / Decimal(num_weeks)

        logger.debug("x: {0}, y: {1}".format(x_, y_))

        # month
        p = l + int((w - 1 - l - r) / 2), 20
        self.currentView.set(themonth)
        self.busyHsh = {}

        # occasions
        busy_ids = set([])
        monthid2date = {}

        # self.canvas.bind('<Escape>', self.on_leave_item)

        # monthdays
        intervals = [360, 720, 1080, 1440]
        busywidth = 2
        offset = 6
        indent = 7
        # barcolor = "PaleGreen3"
        # barcolor = "SkyBlue3"
        # barcolor = "SlateBlue4"
        # barcolor = "SlateGray3"

        # nightcolor = "indianred1"
        # morningcolor = "khaki3"
        # afternooncolor = "PaleGreen3"
        # eveningcolor = "SteelBlue3"

        barcolor = "SteelBlue3"
        nightcolor = barcolor
        morningcolor = barcolor
        afternooncolor = barcolor
        eveningcolor = barcolor

        for j in range(num_weeks):
            for i in range(7):
                busytimes = 0
                flagcolor = "white"
                start_x = l + i * x_
                end_x = start_x + x_
                start_y = int(t + y_ * j)
                end_y = start_y + y_
                xy = int(start_x), int(start_y), int(end_x), int(end_y)
                p = int(l + x_ / 2 + x_ * i), int(t + y_ * j + y_ / 2)
                # pp = int(l +  x_ + x_ * i), int(t + y_ * j + y_ )

                tl_x = bl_x = int(l + x_ * i)
                tl_y = tr_y = int(t + y_ *j)
                tr_x = br_x = int(tl_x + x_)
                bl_y = br_y = int(tl_y + y_)
                w_ = x_ - 12
                h_ = y_ - 12

                thisdate = weeks[j][i]
                isokey = thisdate.isocalendar()
                month = thisdate.month
                tags = []
                if (month != self.year_month[1]):
                    fill = "gray70"
                else:
                    fill = "SteelBlue4"
                    id = self.canvas.create_rectangle(xy, outline="", width=0)
                    busy_ids.add(id)
                    monthid2date[id] = thisdate
                    today = (thisdate == self.current_day.date())
                    bt = []
                    if today:
                        flagcolor = CURRENTFILL
                        tags.append('current_day')
                    if loop.occasions is not None and isokey in loop.occasions:
                        bt = []
                        for item in loop.occasions[isokey]:
                            it = list(item)
                            if matching:
                                if not self.cal_regex.match(it[-1]):
                                    continue
                                mtch = (self.default_regex.match(it[-1]) is not None)
                            else:
                                mtch = True
                            it.append(mtch)
                            item = tuple(it)
                            bt.append(item)
                        occasion_lst.append(bt)
                        if bt:
                            if not today:
                                flagcolor = OCCASIONFILL
                                tags.append('occasion')
                            self.busyHsh.setdefault(id, []).extend(["^ {0}".format(x[0]) for x in bt])
                    else:
                        occasion_lst.append([])

                    if loop.busytimes is not None and isokey in loop.busytimes:
                        bt = []
                        overlap = False
                        for item in loop.busytimes[isokey]:
                            it = list(item)
                            if it[0] == it[1]:
                                # skip reminders
                                continue
                            if matching:
                                if not self.cal_regex.match(it[-1]):
                                    continue
                                mtch = (self.default_regex.match(it[-1]) is not None)
                            else:
                                mtch = True
                            it.append(mtch)
                            item = tuple(it)
                            bt.append(item)
                        busy_lst.append(bt)
                        busy_dates.append(thisdate.strftime("%a %d"))
                        if bt:
                            lastend = 0
                            for pts in bt:
                                busytimes += pts[1] - pts[0]
                                self.busyHsh.setdefault(id, []).append("* {0}".format(pts[2]))
                                if pts[0] < lastend:
                                    overlap = True
                                lastend = pts[1]
                            if overlap:
                                flagcolor = "red"
                            tags.append('busy')

                            busylines = [[], [], [], []]
                            # each side 360 minutes plus 2 times bar width
                            for pts in bt:
                                pt1 = max(0, pts[0])
                                pt2 = min(pts[1], 1440)
                                tmp = [[], [], [], []]

                                for ii in range(0, 4):
                                    if pt1 >= intervals[ii]:
                                        continue
                                    tmp[ii].append(pt1)
                                    for jj in range(ii, 4):
                                        if jj > ii:
                                            tmp[jj].append(intervals[jj-1])
                                        if pt2 <= intervals[jj]:
                                            tmp[jj].append(pt2)
                                            break
                                        else:
                                            tmp[jj].append(intervals[jj])
                                    break
                                for ii in range(4):
                                    if tmp[ii]:
                                        busylines[ii].append(tmp[ii])

                            if busylines:
                                for side in range(4):
                                    lines = busylines[side]
                                    if lines:
                                        if side == 0: # left
                                            for line in lines:
                                                bx = ex = bl_x + offset
                                                by = bl_y - indent - int(Decimal((line[0])/360) * h_)
                                                ey = bl_y - indent - int(Decimal((line[1])/360) * h_)
                                                self.canvas.create_line((bx, by, ex, ey), fill=nightcolor, width=busywidth, tag="busy")
                                        elif side == 1: # top
                                            for line in lines:
                                                by = ey = tl_y + offset
                                                bx = tl_x + indent + int(Decimal((line[0]-360)/360) * w_)
                                                ex = tl_x + indent + int(Decimal((line[1]-360)/360) * w_)
                                                self.canvas.create_line((bx, by, ex, ey), fill=morningcolor, width=busywidth, tag="busy")
                                        elif side == 2: # right
                                            for line in lines:
                                                bx = ex = tr_x - offset
                                                by = tr_y + indent + int(Decimal((line[0]-720)/360) * h_)
                                                ey = tr_y + indent + int(Decimal((line[1]-720)/360) * h_)
                                                self.canvas.create_line((bx, by, ex, ey), fill=afternooncolor, width=busywidth, tag="busy")
                                        elif side == 3: # bottom
                                            for line in lines:
                                                by = ey = br_y - offset
                                                bx = br_x - indent - int(Decimal((line[0]-1080)/360) * w_)
                                                ex = br_x - indent - int(Decimal((line[1]-1080)/360) * w_)
                                                self.canvas.create_line((bx, by, ex, ey), fill=eveningcolor, width=busywidth, tag="busy")

                                bx = bl_x + offset - 1.5 * busywidth
                                ex = bl_x + offset + .5 * busywidth
                                by = bl_y - indent + 1.5 * busywidth
                                ey = bl_y - indent - .5 * busywidth
                                if flagcolor:
                                    self.canvas.create_rectangle((bx, by, ex, ey), fill=flagcolor, outline=flagcolor, tag="busy")
                    else:
                        busy_lst.append([])
                        busy_dates.append(thisdate.strftime("%a %d"))
                if 'current_day' in tags:
                    self.canvas.itemconfig(id, tag='current_day', fill=CURRENTFILL)
                elif 'occasion' in tags:
                    self.canvas.itemconfig(id, tag='occasion', fill=OCCASIONFILL)
                elif 'busy' in tags:
                    self.canvas.itemconfig(id, tag='busy', fill="white")

                if fill:
                    self.canvas.create_text(p, text="{0}".format(weeks[j][i].day), fill=fill)

        busy_ids = list(busy_ids)
        for id in busy_ids:
            self.canvas.tag_bind(id, '<Any-Enter>', self.on_enter_item)
            self.canvas.tag_bind(id, '<Any-Leave>', self.on_leave_item)

        # border
        # xy = int(l), int(t), int(l + x_ * 7), int(t + y_ * num_weeks + 1)
        # self.canvas.create_rectangle(xy, tag="grid")

        # verticals
        for i in range(1, 7):
            xy = int(l + x_ * i), int(t-18), int(l + x_ * i), int(t + y_ * num_weeks)
            self.canvas.create_line(xy, fill=LINECOLOR, tag="grid")
        # horizontals
        for j in range(1, num_weeks):
            xy = int(l), int(t + y_ * j), int(l + x_ * 7), int(t + y_ * j)
            self.canvas.create_line(xy, fill=LINECOLOR, tag="grid")

        # days
        for i in range(7):
            p = int(l + x_ / 2 + x_ * i), int(t - 10)
            self.canvas.create_text(p, text="{0}".format(weekdays[i]),fill = "SteelBlue4")

        self.busy_info = (themonth, busy_dates, busy_lst, occasion_lst)
        self.busy_ids = busy_ids
        self.busy_ids.sort()
        self.canvas_ids = self.busy_ids
        self.monthid2date = monthid2date


    def selectId(self, event, d=1):
        ids = self.busy_ids
        if self.canvas_idpos is None:
            self.canvas_idpos = 0
            old_id = None
        else:
            old_id = self.canvas_ids[self.canvas_idpos]
            if old_id in ids:
                tags = self.canvas.gettags(old_id)
                if 'current_day' in tags:
                    self.canvas.itemconfig(old_id, fill=CURRENTFILL)
                elif 'occasion' in tags:
                    self.canvas.itemconfig(old_id, fill=OCCASIONFILL)
                elif 'other' in tags:
                    self.canvas.itemconfig(old_id, fill=OTHERFILL)
                elif self.weekly:
                    self.canvas.itemconfig(old_id, fill=DEFAULTFILL)
            else:
                self.canvas.itemconfig(old_id, fill=OCCASIONFILL)
                self.canvas.tag_lower(old_id)
            if d == -1:
                self.canvas_idpos -= 1
                if self.canvas_idpos < 0:
                    self.priorWeekMonth(event=event)
                    self.canvas_idpos = len(self.canvas_ids) - 1
            elif d == 1:
                self.canvas_idpos += 1
                if self.canvas_idpos > len(self.canvas_ids) - 1:
                    self.nextWeekMonth(event=event)
                    self.canvas_idpos = 0

        if old_id is not None and old_id in self.busy_ids:
            tags = self.canvas.gettags(old_id)
            if 'current_day' in tags:
                self.canvas.itemconfig(old_id, fill=CURRENTFILL)
            elif 'occasion' in tags:
                self.canvas.itemconfig(old_id, fill=OCCASIONFILL)
            elif 'busy' in tags:
                self.canvas.itemconfig(old_id, fill="white")
            else:
                self.canvas.itemconfig(old_id, fill="white")

        self.selectedId = id = self.canvas_ids[self.canvas_idpos]
        self.active_date = self.monthid2date[id]
        self.canvas_date = self.monthid2date[id]
        self.scrollToDate(self.active_date)
        self.canvas_idpos = self.canvas_ids.index(id)
        if id in self.busy_ids:
            self.canvas.itemconfig(id, fill=ACTIVEFILL)
        if id in self.busyHsh:
            txt = "\n".join(self.busyHsh[id])
            self.content.delete("1.0", END)
            self.content.insert("1.0", txt)
        else:
            self.content.delete("1.0", END)
        self.setFocus(e=event)


    def setFocus(self, e):
        self.canvas.focus()
        self.canvas.focus_set()

    def on_enter_item(self, e):
        if self.canvas_idpos is not None:
            old_id = self.canvas_ids[self.canvas_idpos]
            if old_id in self.busy_ids:
                tags = self.canvas.gettags(old_id)
                if 'current_day' in tags:
                    self.canvas.itemconfig(old_id, fill=CURRENTFILL)
                elif 'occasion' in tags:
                    self.canvas.itemconfig(old_id, fill=OCCASIONFILL)
                else:
                    self.canvas.itemconfig(old_id, fill="white")
        self.selectedId = id = self.canvas.find_withtag(CURRENT)[0]
        self.active_date = self.monthid2date[id]
        self.canvas_date = self.monthid2date[id]
        self.canvas_idpos = self.canvas_ids.index(id)
        if id in self.busy_ids:
            self.canvas.itemconfig(id, fill=ACTIVEFILL)
        if id in self.busyHsh:
            txt = "\n".join(self.busyHsh[id])
            self.content.delete("1.0", END)
            self.content.insert("1.0", txt)
        else:
            self.content.delete("1.0", END)

    def on_leave_item(self, e):
        self.content.delete("1.0", END)
        id = self.canvas.find_withtag(CURRENT)[0]
        if id in self.busy_ids:
            tags = self.canvas.gettags(id)
            if 'current_date' in tags:
                self.canvas.itemconfig(id, fill=CURRENTFILL)
            elif 'occasion' in tags:
                self.canvas.itemconfig(id, fill=OCCASIONFILL)
            else:
                self.canvas.itemconfig(id, fill="white")
        else:
            self.canvas.itemconfig(id, fill="white")

    # def on_clear_item(self, e=None):
    #     if not self.weekly or self.monthly:
    #         return
    #     if self.selectedId:
    #         id = self.selectedId
    #         if id in self.busy_ids:
    #             tags = self.canvas.gettags(id)
    #             if 'other' in tags:
    #                 self.canvas.itemconfig(id, fill=OTHERFILL)
    #             else:
    #                 self.canvas.itemconfig(id, fill=DEFAULTFILL)
    #         else:
    #             self.canvas.itemconfig(id, fill=OCCASIONFILL)
    #     self.canvas.tag_raise('conflict')
    #     self.canvas.tag_raise('grid')
    #     self.canvas.tag_lower('occasion')
    #     self.selectedId = None
    #     self.OnSelect()
    #     self.canvas.focus("")

    def on_select_item(self, event):
        if self.monthly or self.weekly:
            self.newItem()
        else:
            return "break"

    def on_activate_item(self, event):
        if self.monthly or self.weekly:
            self.newItem()

    def newEvent(self, event):
        logger.debug("event: {0}".format(event))
        self.canvas.focus_set()
        px = event.x
        py = event.y
        (w, h, l, r, t, b) = self.margins
        x = Decimal(w - 1 - l - r) / Decimal(7)  # x per day intervals
        rx = int(round(Decimal(px - l) / x - Decimal(0.5)))  # number of days
        dt = (self.week_beg + rx * ONEDAY).replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
        dfmt = dt.strftime("%a %b %d")
        s = "*  @s {0}".format(dfmt)
        changed = SimpleEditor(parent=self, master=self.canvas, start=s, options=loop.options).changed

        if changed:
            logger.debug('changed, updating alerts, ...')

            self.updateAlerts()
            if self.weekly:
                self.updateDay()
                self.showWeek(event=event)
            elif self.monthly:
                self.updateDay()
                self.showMonth(event=event)
            else:
                self.showView()

    def showCalendar(self, e=None):
        cal_year = 0
        opts = loop.options
        cal_pastcolor = '#FFCCCC'
        cal_currentcolor = '#FFFFCC'
        cal_futurecolor = '#99CCFF'

        def showYear(x=0):
            global cal_year
            if x:
                cal_year += x
            else:
                cal_year = 0
            cal = "\n".join(calyear(cal_year, options=opts))
            if cal_year > 0:
                col = cal_futurecolor
            elif cal_year < 0:
                col = cal_pastcolor
            else:
                col = cal_currentcolor
            t.configure(bg=col)
            t.delete("0.0", END)
            t.insert("0.0", cal)

        win = Toplevel()
        win.title(_("Calendar"))
        f = Frame(win)
        # pack the button first so that it doesn't disappear with resizing
        b = Button(win, text=_('OK'), width=10, command=win.destroy, default='active', pady=2)
        b.pack(side='bottom', fill=tkinter.NONE, expand=0, pady=0)
        win.bind('<Return>', (lambda e, b=b: b.invoke()))
        win.bind('<Escape>', (lambda e, b=b: b.invoke()))

        t = ReadOnlyText(f, wrap="word", padx=2, pady=2, bd=2, relief="sunken",

                         font=self.tkfixedfont,

                         takefocus=False)
        win.bind('<Left>', (lambda e: showYear(-1)))
        win.bind('<Right>', (lambda e: showYear(1)))
        win.bind('<Home>', (lambda e: showYear()))
        showYear()
        t.pack(side='left', fill=tkinter.BOTH, expand=1, padx=0, pady=0)
        ysb = Scrollbar(f, orient='vertical', command=t.yview, width=8)
        ysb.pack(side='right', fill=tkinter.Y, expand=0, padx=0, pady=0)

        t.configure(yscroll=ysb.set)
        f.pack(padx=2, pady=2, fill=tkinter.BOTH, expand=1)
        win.focus_set()
        win.grab_set()
        win.transient(self)
        win.wait_window(win)

    def newCommand(self, e=None):
        self.newValue.set(self.newLabel)

    def showShortcuts(self, e=None):
        if e and e.char != "?":
            return
        res = self.menutree.showMenu("_")
        self.textWindow(parent=self, title='etm', opts=self.options, prompt=res, modal=False)

    def help(self, event=None):
        path = USERMANUAL
        if windoz:
            os.startfile(USERMANUAL)
            return()
        if mac:
            cmd = 'open' + " {0}".format(path)
        else:
            cmd = 'xdg-open' + " {0}".format(path)
        subprocess.call(cmd, shell=True)
        return True

    def about(self, event=None):
        res = loop.do_v("")
        self.textWindow(parent=self, title='etm', opts=self.options, prompt=res, modal=False)

    def checkForUpdate(self, event=None):
        res = checkForNewerVersion()[1]
        self.textWindow(parent=self, title='etm', prompt=res, opts=self.options)

    def showChanges(self, event=None):
        if not loop.options['vcs_system']:
            prompt = """An entry for 'vcs_system' in etmtk.cfg is required but missing."""
            self.textWindow(parent=self, title="etm", prompt=prompt, opts=loop.options)
            return

        if self.itemSelected:
            f = self.itemSelected['fileinfo'][0]
            fn = ' {0}"{1}"'.format(self.options['vcs']['file'], os.path.normpath(os.path.join(self.options['datadir'], f)))
            title = _("Showing changes for {0}.").format(f)

        else:
            fn = ""
            title = _("Showing changes for all files.")
        logger.debug('fn: {0}'.format(fn))
        prompt = _("""\
{0}

If an item is selected, changes will be shown for the file containing
the item. Otherwise, changes will be shown for all files.

Enter an integer number of changes to display
or 0 to display all changes.""").format(title)
        depth = GetInteger(
            parent=self,
            title=_("Changes"),
            prompt=prompt, opts=[0], default=10).value
        if depth is None:
            return ()
        if depth == 0:
            # all changes
            numstr = ""
        else:
            numstr = "{0} {1}".format(loop.options['vcs']['limit'], depth)
        command = loop.options['vcs']['history'].format(
            repo=loop.options['vcs']['repo'],
            work=loop.options['vcs']['work'],
            numchanges=numstr, rev="{rev}", desc="{desc}", file=fn)
        logger.debug('vcs history command: {0}'.format(command))
        tt = TimeIt(loglevel=2, label="showChanges")
        s = subprocess.check_output(command, shell=True, universal_newlines=True)
        tt.stop()
        p = s2or3(s)
        if not p:
            p = 'no output from command:\n    {0}'.format(command)

        p = "\n".join([x for x in p.split('\n') if not (x.startswith('index') or x.startswith('diff') or x.startswith('\ No newline'))])

        self.textWindow(parent=self, title=title, prompt=s2or3(p), opts=self.options)

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

    def goHome(self, event=None):
        today = get_current_time().date()
        if self.weekly:
            self.showWeek(event=event, week=0)
            self.scrollToDate(today)
        elif self.monthly:
            self.showMonth(event=event, month=0)
            self.scrollToDate(today)
        elif self.view == DAY:
            self.scrollToDate(today)
        else:
            self.tree.focus_set()
            self.tree.focus(1)
            self.tree.selection_set(1)
            self.tree.yview(0)
        return

    def nextItem(self, e=None):
        item = self.tree.selection()[0]
        if item:
            next = self.tree.next(item)
            if next:
                next = int(next)
                next -= 1
                self.tree.focus(next)
                self.tree.selection_set(next)

    def prevItem(self, e=None):
        item = self.tree.selection()[0]
        if item:
            prev = self.tree.prev(item)
            if prev:
                prev = int(prev)
                prev += 1
                self.tree.focus(prev)
                self.tree.selection_set(prev)

    def OnSelect(self, event=None, uuid=None, dt=None):
        """
        Tree row has gained selection.
        """
        logger.debug("starting OnSelect with uuid: {0}".format(uuid))
        self.content.delete("1.0", END)
        if uuid is None:  # tree view
            if not self.tree.selection():
                return
            item = self.tree.selection()[0]
            self.rowSelected = int(item)
            logger.debug('rowSelected: {0}'.format(self.rowSelected))
            # type_chr is the actual type, e.g., "-"
            type_chr = self.tree.item(item)['text'][0]
            uuid, dt, hsh = self.getInstance(item)
            logger.debug('tree rowSelected: {0}; {1}; {2}'.format(self.rowSelected, self.tree.item(item)['text'], dt))
            if self.view in [AGENDA]:
                if self.rowSelected in self.id2date:
                    if dt is None:
                        # we have the date selected
                        self.active_date = self.id2date[self.rowSelected]
                        logger.debug("active_date from id2date: {0}; {1}".format(self.active_date, self.tree.item(item)['text']))
                    else:
                        # we have an item
                        self.active_date = dt.date()
                        logger.debug('active date from dt: {0}; {1}'.format(self.active_date, self.tree.item(item)))
                else:
                    if dt:
                        self.active_date = dt.date()
                        logger.debug('active date from dt: {0}; {1}'.format(self.active_date, self.tree.item(item)))
                    else:
                        self.active_date = None
                logger.debug('active_date: {0}'.format(self.active_date))
            if hsh:
                type_chr = hsh['itemtype']
        self.update_idletasks()

        if uuid is not None:
            isRepeating = ('r' in hsh and dt)
            if isRepeating:
                logger.debug('selected: {0}, {1}'.format(dt, type(dt)))
                item = "{0} {1}".format(_('selected'), dt)
                self.itemmenu.entryconfig(1, label="{0} ...".format(self.em_opts[1]))
                self.itemmenu.entryconfig(2, label="{0} ...".format(self.em_opts[2]))
            else:
                self.itemmenu.entryconfig(1, label=self.em_opts[1])
                self.itemmenu.entryconfig(2, label=self.em_opts[2])
                item = _('selected')
            isUnfinished = (type_chr in ['-', '+', '%'])
            hasLink = ('g' in hsh and hsh['g'])
            hasUser = ('u' in hsh and hsh['u'])
            l1 = hsh['fileinfo'][1]
            l2 = hsh['fileinfo'][2]
            if l1 == l2:
                lines = "{0} {1}".format(_('line'), l1)
            else:
                lines = "{0} {1}-{2}".format(_('lines'), l1, l2)
            self.filetext = filetext = "{0}, {1}".format(hsh['fileinfo'][0], lines)
            if 'errors' in hsh and hsh['errors']:
                text = "{1}\n\n{2}: {3}\n\n{4}: {5}".format(item, hsh['entry'].lstrip(), _("Errors"), hsh['errors'], _("file"), filetext)
            else:
                text = "{1}\n\n{2}: {3}".format(item, hsh['entry'].lstrip(), _("file"), filetext)
            for i in [0, 1, 2, 3, 5, 6, 7, 8]:  # everything except finish (4), open link (9) and show user (10)
                self.itemmenu.entryconfig(i, state='normal')
            if isUnfinished:
                self.itemmenu.entryconfig(4, state='normal')
            else:
                self.itemmenu.entryconfig(4, state='disabled')
            if hasLink:
                self.itemmenu.entryconfig(9, state='normal')
            else:
                self.itemmenu.entryconfig(9, state='disabled')
            if hasUser:
                self.itemmenu.entryconfig(10, state='normal')
            else:
                self.itemmenu.entryconfig(10, state='disabled')
            self.uuidSelected = uuid
            self.itemSelected = hsh
            logger.debug('dt selected: {0}, {1}'.format(dt, type(dt)))
            self.dtSelected = dt
        else:
            text = ""
            for i in range(11):
                self.itemmenu.entryconfig(i, state='disabled')
            self.itemSelected = None
            self.uuidSelected = None
            self.dtSelected = None
        r = self.tree.identify_row(1)
        if r:
            self.topSelected = int(r)
        else:
            self.topSelected = 1
        logger.debug("row: {0}; uuid: {1}; instance: {2}, {3}; top: {4}".format(self.rowSelected, self.uuidSelected, self.dtSelected, type(self.dtSelected), self.topSelected))
        self.content.insert(INSERT, text)
        self.update_idletasks()
        logger.debug('ending OnSelect')
        return

    def OnActivate(self, event):
        """
        Return pressed with tree row selected
        """
        if not self.itemSelected:
            return "break"
        item = self.tree.selection()[0]
        uuid, dt, hsh = self.getInstance(item)
        x = self.winfo_rootx() + 350
        y = self.winfo_rooty() + 50
        logger.debug("id: {0}, coords: {1}, {2}".format(id, x, y))
        self.itemmenu.post(x, y)
        self.itemmenu.focus_set()
        return "break"

    def getInstance(self, item):
        instance = self.count2id[item]
        logger.debug('starting getInstance: {0}; {1}'.format(item, instance))
        if instance is not None:
            uuid, dt = self.count2id[item].split("::")
            hsh = loop.uuid2hash[uuid]
            dt = parse(dt)
            logger.debug('returning uuid: {0}, dt: {1}'.format(uuid, dt))
            return uuid, dt, hsh
        else:
            logger.debug('returning None')
            return None, None, None

    def updateTimerStatus(self):
        title, status = self.actionTimer.getStatus()
        # print('setting title:', title, " and status:", status)
        self.timerTitle.set(title)
        self.timerStatus.set(status)

    def updateClock(self):
        tt = TimeIt(loglevel=2, label="updateClock")
        self.now = get_current_time()
        self.current_minutes = self.now.hour * 60 + self.now.minute
        nxt = (60 - self.now.second) * 1000 - self.now.microsecond // 1000
        nowfmt = "{0} {1}".format(
            s2or3(self.now.strftime("%a %b %d")),
            s2or3(self.now.strftime(loop.options['reprtimefmt']).lower()),
        )
        logger.debug('next update in {0} milliseconds.'.format(nxt))
        self.after(nxt, self.updateClock)

        nowfmt = leadingzero.sub("", nowfmt)
        self.currentTime.set("{0}".format(nowfmt))
        self.title(self.currentTime.get())
        today = self.now.date()
        newday = (today != self.today)
        self.today = today

        new, modified, deleted = get_changes(
            self.options, loop.file2lastmodified)

        if newday or new or modified or deleted:
            if newday:
                logger.info('newday')
                self.actionTimer.newDay()

            logger.info("new: {0}; modified: {1}; deleted: {2}".format(len(new), len(modified), len(deleted)))
            logger.debug('calling loadData')
            loop.loadData()
            # we now have file2uuids ...

            if self.weekly:
                logger.debug('calling showWeek')
                self.updateDay()
                self.showWeek()
                if newday:
                    self.scrollToDate(today)
            elif self.monthly:
                logger.debug('calling showMonth')
                self.updateDay()
                self.showMonth()
                if newday:
                    self.scrollToDate(today)
            else:
                logger.debug('calling showView')
                self.showView()

        if self.current_minutes % loop.options['update_minutes'] == 0:
            if loop.do_update:
                updateCurrentFiles(loop.rows, loop.file2uuids, loop.uuid2hash, loop.options)
                loop.do_update = False

            if loop.options['icssync_folder']:
                fullpath = os.path.join(loop.options['datadir'], loop.options['icssync_folder'])
                prefix, files = getAllFiles(fullpath, include="*")
                base_files = set([])
                # file_lst = []
                for tup in files:
                    base, ext = os.path.splitext(tup[0])
                    if ext in [".txt", ".ics"]:
                        base_files.add(base)
                file_lst = list(base_files)
                datadir = loop.options['datadir']
                for file in file_lst:
                    relfile = relpath(file, datadir)
                    logger.debug('calling syncTxt: {0}; {1}'.format(datadir, relfile))
                    syncTxt(self.loop.file2uuids, self.loop.uuid2hash, datadir, relfile)
                # any updated txt files will be reloaded in the next update

        self.updateAlerts()

        if self.actionTimer.currentStatus != STOPPED:
            title, status = self.actionTimer.getStatus()
            self.timerTitle.set(title)
            self.timerStatus.set(status)
            if self.actionTimer.currentMinutes >= 1:
                if (self.options['action_interval'] and self.actionTimer.currentMinutes % loop.options['action_interval'] == 0):
                    logger.debug('action_minutes trigger: {0} {1}'.format(self.actionTimer.currentMinutes, self.actionTimer.currentStatus))
                    if self.actionTimer.currentStatus == 'running':
                        if ('running' in loop.options['action_timer'] and
                                loop.options['action_timer']['running']):
                            tcmd = loop.options['action_timer']['running']
                            logger.debug('running: {0}'.format(tcmd))

                            subprocess.call(tcmd, shell=True)

                    elif self.actionTimer.currentStatus == 'paused':
                        if ('paused' in loop.options['action_timer'] and
                                loop.options['action_timer']['paused']):
                            tcmd = loop.options['action_timer']['paused']

                            logger.debug('paused: {0}'.format(tcmd))
                            subprocess.call(tcmd, shell=True)
        tt.stop()

    def updateAlerts(self):
        self.update_idletasks()
        if loop.alerts:
            logger.debug('updateAlerts: {0}'.format(len(loop.alerts)))
        alerts = deepcopy(loop.alerts)
        if alerts and loop.options['calendars']:
            alerts = [x for x in alerts if self.cal_regex.match(x[-1])]
        if alerts:
            curr_minutes = datetime2minutes(self.now)
            td = -1
            # pop old alerts
            while td < 0 and alerts:
                td = alerts[0][0] - curr_minutes
                if td < 0:
                    a = alerts.pop(0)
            # alerts for this minute will have td's < 1.0
            if td < 1.0:
                if ('alert_wakecmd' in loop.options and
                        loop.options['alert_wakecmd']):
                    cmd = s2or3(loop.options['alert_wakecmd'])
                    subprocess.call(cmd, shell=True)
                while td < 1.0 and alerts:
                    hsh = alerts[0][2]
                    alerts.pop(0)
                    actions = hsh['_alert_action']
                    if 's' in actions:
                        if ('alert_soundcmd' in self.options and
                                self.options['alert_soundcmd']):
                            scmd = s2or3(expand_template(
                                self.options['alert_soundcmd'], hsh))
                            subprocess.call(scmd, shell=True)
                        else:
                            self.textWindow(parent=self, title="etm", prompt=_("""\
A sound alert failed. The setting for 'alert_soundcmd' is missing from  your etmtk.cfg."""), opts=self.options)
                    if 'd' in actions:
                        if ('alert_displaycmd' in self.options and
                                self.options['alert_displaycmd']):
                            dcmd = s2or3(expand_template(
                                self.options['alert_displaycmd'], hsh))
                            subprocess.call(dcmd.encode(loop.options['encoding']['gui']), shell=True)
                        else:
                            self.textWindow(parent=self, title="etm", prompt=_("""\
A display alert failed. The setting for 'alert_displaycmd' is missing \
from your etmtk.cfg."""), opts=self.options)
                    if 'v' in actions:
                        if ('alert_voicecmd' in self.options and
                                self.options['alert_voicecmd']):
                            vcmd = s2or3(expand_template(
                                self.options['alert_voicecmd'], hsh))
                            subprocess.call(vcmd, shell=True)
                        else:
                            self.textWindow(parent=self, title="etm", prompt=_("""\
An email alert failed. The setting for 'alert_voicecmd' is missing from \
your etmtk.cfg."""), opts=self.options)
                    if 'e' in actions:
                        missing = []
                        for field in ['smtp_from', 'smtp_id', 'smtp_pw', 'smtp_server', 'smtp_to']:
                            if not self.options[field]:
                                    missing.append(field)
                        if missing:
                            self.textWindow(parent=self, title="etm", prompt=_("""\
An email alert failed. Settings for the following variables are missing \
from your etmtk.cfg: %s.""" % ", ".join(["'%s'" % x for x in missing])), opts=self.options)
                        else:
                            subject = hsh['summary']
                            message = expand_template(
                                self.options['email_template'], hsh)
                            arguments = hsh['_alert_argument']
                            recipients = [str(x).strip() for x in arguments[0]]
                            if len(arguments) > 1:
                                attachments = [str(x).strip()
                                               for x in arguments[1]]
                            else:
                                attachments = []
                            if subject and message and recipients:
                                send_mail(
                                    smtp_to=recipients,
                                    subject=subject,
                                    message=message,
                                    files=attachments,
                                    smtp_from=self.options['smtp_from'],
                                    smtp_server=self.options['smtp_server'],
                                    smtp_id=self.options['smtp_id'],
                                    smtp_pw=self.options['smtp_pw'])
                    if 't' in actions:
                        missing = []
                        for field in ['sms_from', 'sms_message', 'sms_phone', 'sms_pw', 'sms_server', 'sms_subject']:
                            if not self.options[field]:
                                missing.append(field)
                        if missing:
                            self.textWindow(parent=self, title="etm", prompt=_("""\
A text alert failed. Settings for the following variables are missing \
from your 'emt.cfg': %s.""" % ", ".join(["'%s'" % x for x in missing])), opts=self.options)
                        else:
                            message = expand_template(
                                self.options['sms_message'], hsh)
                            subject = expand_template(
                                self.options['sms_subject'], hsh)
                            arguments = hsh['_alert_argument']
                            if arguments:
                                sms_phone = ",".join([str(x).strip() for x in
                                                      arguments[0]])
                            else:
                                sms_phone = self.options['sms_phone']
                            if message:
                                send_text(
                                    sms_phone=sms_phone,
                                    subject=subject,
                                    message=message,
                                    sms_from=self.options['sms_from'],
                                    sms_server=self.options['sms_server'],
                                    sms_pw=self.options['sms_pw'])
                    if 'p' in actions:
                        arguments = hsh['_alert_argument']
                        proc = str(arguments[0][0]).strip()
                        cmd = s2or3(expand_template(proc, hsh))
                        subprocess.call(cmd, shell=True)
                    if 'm' in actions:
                        # put this last since the internal message window is modal and thus blocking
                        MessageWindow(
                            self,
                            title=expand_template('!summary!', hsh),
                            prompt=expand_template(
                                self.options['alert_template'], hsh))

                    if not alerts:
                        break
                    td = alerts[0][0] - curr_minutes
        if alerts and len(alerts) > 0:
            self.pendingAlerts.set(len(alerts))
            # self.pending.configure(state="normal")
            self.activeAlerts = alerts
        else:
            self.pendingAlerts.set(0)
            self.activeAlerts = []
            # self.pending.configure(state="disabled")

    def textWindow(self, parent, title=None, prompt=None, opts=None, modal=True):
        TextDialog(parent, title=title, prompt=prompt, opts=opts, modal=modal)

    def goToDate(self, e=None):
        if e and e.char != "j":
            return
        prompt = _("""\
Return an empty string for the current date or a date to be parsed.
Relative dates and fuzzy parsing are supported.""")
        if self.view not in [DAY, WEEK, MONTH]:
            return
        d = GetDateTime(parent=self, title=_('date'), prompt=prompt)
        day = d.value

        logger.debug('day: {0}'.format(day))
        if day is None:
            return
        self.chosen_day = day

        if self.weekly:
            self.showWeek(event=e, week=None)
        elif self.monthly:
            self.showMonth(event=e, month=None)
        self.scrollToDate(day.date())
        return

    def setFilter(self, *args):
        if self.view in [CUSTOM]:
            return
        self.filter_active = True
        # self.motionmenu.entryconfig(6, state="disabled")
        # self.motionmenu.entryconfig(7, state="normal")
        self.fltr.configure(bg="white", state="normal")
        self.fltr.focus_set()

    def clearFilter(self, e=None):
        if self.view in [CUSTOM]:
            return
        self.filter_active = False
        # self.motionmenu.entryconfig(6, state="normal")
        # self.motionmenu.entryconfig(7, state="disabled")
        self.filterValue.set('')
        self.fltr.configure(bg=BGCOLOR)
        self.tree.focus_set()
        if self.rowSelected:
            self.tree.focus(self.rowSelected)
            self.tree.selection_set(self.rowSelected)
            self.tree.see(self.rowSelected)

    def leaveFilter(self, e=None):
        self.tree.focus_set()
        if self.rowSelected:
            self.tree.focus(self.rowSelected)
            self.tree.selection_set(self.rowSelected)
            self.tree.see(self.rowSelected)


    def kloneTimer(self, e=None):
        """
        """
        # hack to avoid activating with Ctrl-k
        if e and e.char != "k":
            return
        if not self.uuidSelected:
            return
        hsh = loop.uuid2hash[self.uuidSelected]
        self.timerItem = self.uuidSelected
        logger.debug('item: {0}'.format(hsh))
        self.actionTimer.selectTimer(name=hsh['_summary'])

    def finishActionTimer(self, e=None):
        if e and e.char != "T":
            return
        thsh = self.actionTimer.finishTimer(e=e)
        if not thsh:
            return
        self.updateTimerStatus()

        hsh = {"itemtype": "~", "_summary": thsh['summary'], "s": thsh['start'], "e": thsh['total']}
        changed = SimpleEditor(parent=self, newhsh=hsh, rephsh=None, options=loop.options, title=_("new action"), modified=True).changed
        if changed:
            # clear status and reload
            self.actionTimer.deleteTimer(timer = self.actionTimer.selected)

            self.updateAlerts()
            if self.weekly:
                self.updateDay()
                self.showWeek()
            elif self.monthly:
                self.updateDay()
                self.showMonth()
            else:
                self.showView(row=self.topSelected)

        self.updateTimerStatus()

    def gettext(self, event=None):
        s = self.e.get()
        if s is not None:
            return s
        else:
            return ''

    def cleartext(self, event=None):
        self.showView()
        return 'break'

    def process_input(self, event=None, cmd=None):
        """
        """

        if not cmd:
            return True
        if self.mode == 'command':
            cmd = cmd.strip()
            # if cmd[0] in ['a', 'c']:
            if cmd[0] in ['a']:
                # simple command history for report commands
                if cmd in self.history:
                    self.history.remove(cmd)
                self.history.append(cmd)
                self.index = len(self.history) - 1
            else:
                parts = cmd.split(' ')
                if len(parts) == 2:
                    try:
                        i = int(parts[0])
                    except:
                        i = None
                    if i:
                        parts.pop(0)
                        parts.append(str(i))
                        cmd = " ".join(parts)
            try:
                res = loop.do_command(cmd)
            except:
                return _('could not process command "{0}"').format(cmd)

        elif self.mode == 'delete':
            loop.cmd_do_delete(cmd)
            res = ''

        elif self.mode == 'finish':
            loop.cmd_do_finish(cmd)
            res = ''

        elif self.mode == 'new_date':
            res = loop.new_date(cmd)

        if not res:
            res = _('command "{0}" returned no output').format(cmd)

            logger.debug('no output')

            self.clearTree()
            return ()

        if type(res) == dict:
            self.showTree(res, event=event)
        else:
            # not a hash => not a tree
            self.textWindow(self, title='etm', prompt=res, opts=self.options)
            return 0

    def expand2Depth(self, e=None):
        if e and e.char != "o":
            return
        prompt = _("""\
Enter an integer depth to expand branches
or 0 to expand all branches completely.""")
        depth = GetInteger(
            parent=self,
            title=_("depth"), prompt=prompt, opts=[0], default=0).value
        if depth is None:
            return ()
        maxdepth = max([k for k in self.depth2id])
        logger.debug('expand2Depth {0}: {1}/{2}'.format(self.view, depth, maxdepth))
        if self.view in [AGENDA, DAY, KEYWORD, NOTE, TAG, PATH, CUSTOM]:
            self.outline_depths[self.view] = depth
            logger.debug('outline_depths: {0}'.format(self.outline_depths))
        if depth == 0:
            # expand all
            for k in self.depth2id:
                for item in self.depth2id[k]:
                    self.tree.item(item, open=True)
        else:
            depth -= 1
            depth = max(depth, 0)
            logger.debug('using depth: {0}; {1}'.format(depth, maxdepth))
            for i in range(depth):
                if i in self.depth2id:
                    for item in self.depth2id[i]:
                        try:
                            self.tree.item(item, open=True)
                        except:
                            logger.exception('open: {0}, {1}'.format(i, item))
            for i in range(depth, maxdepth + 1):
                if i in self.depth2id:
                    for item in self.depth2id[i]:
                        try:
                            self.tree.item(item, open=False)
                        except:
                            logger.exception('open: {0}, {1}'.format(i, item))

    def scrollToDate(self, date=None):
        if not loop.prevnext or date is None:
            return
        if self.view not in [DAY, WEEK, MONTH] or date not in loop.prevnext:
            return
        # new: go to the first date on or **after**, i.e., prevnext last
        active_date = loop.prevnext[date][1]
        if active_date not in self.date2id:
            return
        if self.weekly:
            pos = date.isocalendar()[2] - 1
            self.canvas_idpos = pos
        elif self.monthly:
            pos = date.day - 1
            self.canvas_idpos = pos
        uid = self.date2id[active_date]
        self.active_date = active_date
        self.scrollToId(uid)

    def scrollToId(self, uid):
        self.update_idletasks()
        # self.tree.focus_set()
        self.tree.focus(uid)
        self.tree.selection_set(uid)
        self.tree.yview(int(uid) - 1)

    def showTree(self, tree, event=None):
        self.date2id = {}
        self.id2date = {}
        self.clearTree()
        self.count = 0
        self.count2id = {}
        self.active_tree = tree
        self.depth2id = {}
        self.add2Tree(u'', tree[self.root], tree)
        loop.count2id = self.count2id
        self.tree.tag_configure('treefont', font=self.tktreefont)

        self.content.delete("0.0", END)

        if event is None:

            if self.view in [DAY, WEEK, MONTH] and self.active_date:
                self.scrollToDate(self.active_date)
            else:
                if self.view in [AGENDA, TAG, KEYWORD, NOTE, PATH]:
                    if self.filter_active:
                        depth = 0
                    else:
                        depth = self.outline_depths[self.view]
                    if depth == 0:
                        # expand all
                        for k in self.depth2id:
                            for item in self.depth2id[k]:
                                self.tree.item(item, open=True)
                    else:
                        maxdepth = max([k for k in self.depth2id])
                        depth -= 1
                        depth = max(depth, 0)
                        for i in range(depth):
                            for item in self.depth2id[i]:
                                self.tree.item(item, open=True)
                        for i in range(depth, maxdepth + 1):
                            for item in self.depth2id[i]:
                                self.tree.item(item, open=False)
                self.goHome()

    def popupTree(self, e=None):
        # if self.weekly or self.monthly:
        #     return
        if not self.active_tree:
            return
        depth = self.outline_depths[self.view]
        if loop.options:
            if 'report_indent' in loop.options:
                indent = loop.options['report_indent']
            if 'report_width1' in loop.options:
                width1 = loop.options['report_width1']
            if 'report_width2' in loop.options:
                width2 = loop.options['report_width2']
        else:
            indent = 4
            width1 = 43
            width2 = 20
        res = tree2Text(self.active_tree, indent=indent, width1=width1, width2=width2, depth=depth)
        if not res[0][0]:
            res[0].pop(0)
        prompt = "\n".join(res[0])
        self.textWindow(parent=self, title='etm', opts=self.options, prompt=prompt, modal=False)

    def printTree(self, e=None):
        if e and e.char != "p":
            return
        if not self.active_tree:
            return
        ans = self.confirm(parent=self.tree, prompt=_("""Print current outline?"""))
        if not ans:
            return False
        depth = self.outline_depths[self.view]

        if loop.options:
            if 'report_indent' in loop.options:
                indent = loop.options['report_indent']
            if 'report_width1' in loop.options:
                width1 = loop.options['report_width1']
            if 'report_width2' in loop.options:
                width2 = loop.options['report_width2']
        else:
            indent = 4
            width1 = 43
            width2 = 20
        res = tree2Text(self.active_tree, indent=indent, width1=width1, width2=width2, depth=depth)
        if not res[0][0]:
            res[0].pop(0)
        res[0].append('')
        s = "{0}".format("\n".join(res[0]))
        self.printWithDefault(s)

    def clearTree(self):
        """
        Remove all items from the tree
        """
        self.active_tree = {}
        for child in self.tree.get_children():
            self.tree.delete(child)

    def add2Tree(self, parent, elements, tree, depth=0):
        max_depth = 100
        for text in elements:
            self.count += 1
            # text is a key in the element (tree) hash
            # these keys are (parent, item) tuples
            if text in tree:
                # this is a branch
                item = " {0}".format(text[1])  # this is the label of the parent
                children = tree[text]  # these are the children tuples of item
                oid = self.tree.insert(parent, 'end', iid=self.count, text=item,
                                       open=(depth <= max_depth))
                self.depth2id.setdefault(depth, set([])).add(oid)
                # recurse to get children
                self.count2id[oid] = None
                self.add2Tree(oid, children, tree, depth=depth + 1)
            else:
                # this is a leaf
                if len(text[1]) == 4:
                    uuid, item_type, col1, col3 = text[1]
                    dt = ''
                else:  # len 5 day view with datetime appended
                    uuid, item_type, col1, col3, dt = text[1]

                if item_type:
                    # This hack avoids encoding issues under python 2
                    col1 = "{0} {1}".format(id2Type[item_type], col1)

                if type(col3) == int:
                    col3 = '%s' % col3
                else:
                    col3 = s2or3(col3)

                # Drop the instance information from the id
                id = uuid.split(':')[0]
                if id in loop.uuid2labels:
                    col2 = loop.uuid2labels[id]
                else:
                    col2 = "***"
                    if item_type not in ["=", "ib"]:
                        logger.warn('Missing key {0} for {1} {2}'.format(id, col1, col3))
                oid = self.tree.insert(parent, 'end', iid=self.count, text=col1, open=(depth <= max_depth), values=[col2, col3], tags=(item_type, 'treefont'))
                self.count2id[oid] = "{0}::{1}".format(uuid, dt)
                if dt:
                    if item_type == 'by':
                        # we want today, not the starting date for this
                        d = get_current_time().date()
                    else:
                        if type(dt) is datetime:
                            d = dt.date()
                        else:
                            d = parse(dt).date()
                    if d and d not in self.date2id:
                        # logger.debug('date2id[{0}] = {1}'.format(d, parent))
                        self.date2id[d] = int(parent)
                    if int(parent) not in self.id2date:
                        # logger.debug('id2date[{0}] = {1}'.format(int(parent), d))
                        self.id2date[int(parent)] = d

    def makeReport(self, event=None):
        if self.view != CUSTOM:
            return
        self.outline_depths[CUSTOM] = 0
        self.value_of_combo = self.custom_box.get()
        if not self.value_of_combo.strip():
            return
        try:
            res = getReportData(
                self.value_of_combo,
                self.loop.file2uuids,
                self.loop.uuid2hash,
                self.loop.options,
                cli=False)
            if not res:
                res = _("Report contains no output.")
            if self.value_of_combo not in self.specs:
                self.specs.append(self.value_of_combo)
                self.specs.sort()
                self.specs = [x for x in self.specs if x]
                self.custom_box["values"] = self.specs
                self.specsModified = True
            logger.debug("spec: {0}".format(self.value_of_combo))
        except:
            logger.exception("could not process: {0}".format(self.value_of_combo))
            res = _("'{0}' could not be processed".format(self.value_of_combo))
        if type(res) == dict:
            self.showTree(res, event=event)
        else:
            # not a hash => not a tree
            self.textWindow(self, title='etm', prompt=res, opts=self.options)
            self.custom_box.focus_set()
            return 0

    def getSpecs(self, e=None):
        self.specs = []
        if 'reports' in loop.options:
            self.specs = loop.options['reports']

    def saveSpecs(self, e=None):
        # called when changing from custom view or
        # when calling save changes to specs
        if self.view != CUSTOM:
            return
        if not self.specsModified:
            return
        # remove duplicates
        self.specs = list(set(self.specs))
        self.specs.sort()
        added = [x for x in self.specs if x not in self.saved_specs]
        if not added:
            self.specsModified = False
            return
        ans = self.confirm(parent=self, prompt=_("""Save the additions to your report specifications? {0} """.format("\n    ".join(added))))
        if ans:
            file = self.getReportsFile()
            if not (file and os.path.isfile(file)):
                return
            with codecs.open(file, 'r', loop.options['encoding']['file']) as fo:
                lines = fo.readlines()
            lines.extend(added)
            lines.sort()
            content = "\n".join([x.strip() for x in lines if x.strip()])
            with codecs.open(file, 'w', loop.options['encoding']['file']) as fo:
                    fo.write(content)
            logger.debug("saved: {0}".format(file))
            self.getSpecs()
            self.custom_box['values'] = self.specs
            self.value_of_combo = self.specs[0]
            self.specsModified = False

    def exportText(self):
        if self.view != CUSTOM:
            return
        logger.debug("spec: {0}".format(self.value_of_combo))
        tree = getReportData(
            self.value_of_combo,
            self.loop.file2uuids,
            self.loop.uuid2hash,
            self.loop.options,
            export=False)
        text = "\n".join([x for x in tree2Text(tree)[0]])
        prefix, tuples = getFileTuples(loop.options['etmdir'], include=r'*.text', all=True)
        filename = FileChoice(self, "text file", prefix=prefix, list=tuples, ext="text", new=False).returnValue()
        if not filename:
            return False
        fo = codecs.open(filename, 'w', self.options['encoding']['file'])
        fo.write(text)
        fo.close()
        MessageWindow(self, "etm", "Exported text to {0}".format(filename))

    def exportCSV(self):
        if self.view != CUSTOM:
            return
        logger.debug("spec: {0}".format(self.value_of_combo))
        data = getReportData(
            self.value_of_combo,
            self.loop.file2uuids,
            self.loop.uuid2hash,
            self.loop.options,
            export=True)
        prefix, tuples = getFileTuples(loop.options['exportdir'], include=r'*.csv', all=True)
        filename = FileChoice(self, "csv file", prefix=prefix, list=tuples, ext="csv", new=True).returnValue()
        if not filename:
            return
        import csv as CSV
        c = CSV.writer(open(filename, "w"), delimiter=",", lineterminator="\n")
        for line in data:
            c.writerow(line)
        MessageWindow(self, "etm", "Exported CSV to {0}".format(filename))

    def updateSubscriptions(self, e=None):
        if not self.loop.options['ics_subscriptions']:
            MessageWindow(self, 'etm', "A configuration setting for 'ics_subscriptions' is required but missing.")
            return
        good = []
        bad = []
        msg = []
        for url, rp in self.loop.options['ics_subscriptions']:
            fp = os.path.join(self.loop.options['datadir'], rp)
            logger.debug('updating: {0}, {1}'.format(rp, fp))
            res = update_subscription(url, fp)
            if res:
                good.append(rp)
            else:
                bad.append(rp)

        if good:
            msg.append(_("Succesfully updated:\n  {0}").format("\n  ".join(good)))
        if bad:
            msg.append(_("Not updated:\n  {0}").format("\n  ".join(bad)))
        MessageWindow(self, "etm", "\n".join(msg))

    def newselection(self, event=None):
        self.value_of_combo = self.custom_box.get()

loop = None

log_levels = {
    '1': logging.DEBUG,
    '2': logging.INFO,
    '3': logging.WARN,
    '4': logging.ERROR,
    '5': logging.CRITICAL
}


def main(dir=None):  # debug, info, warn, error, critical
    global loop
    etmdir = ''
    logger.debug('in view.main with dir: {0}'.format(dir))
    # For testing override etmdir:
    if dir is not None:
        etmdir = dir
        logger.debug('using etmdir: {0}'.format(etmdir))
    init_localization()
    (user_options, options, use_locale) = data.get_options(etmdir)
    loop = data.ETMCmd(options=options)
    loop.tkversion = tkversion

    app = App()
    app.mainloop()


if __name__ == "__main__":
    setup_logging('3')
    main()
