"""
Journal Toplevel Window
"""
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  $Id: JournalWindow.py 82 2004-07-11 13:01:44Z henning $

import sys
import os
import string
from Tkinter import *
import tkMessageBox
import debug
import broadcaster
import broker
import time

class JournalWindow:

    from JournalListWidget import JournalListWidget
    from JournalEditWidget import JournalEditWidget
    
    def __init__(self, model, tkroot=None):
        if not tkroot:
            tkroot = Tk()
            tkroot.withdraw()
        self.tkroot = tkroot
        self.model = model
        
        self.createWidgets()
        self.centerWindow()
    
        self.registerAtBroadcaster()
        self.lstJournal.updateList()
        self.onJournalsOpen()
    
    def registerAtBroadcaster(self):
        "Register our Callback Handlers"
        # Show Message Box on Notification Broadcast:
        broadcaster.Register(self.onJournalsOpen,
            source='Journals', title='Opened')
        broadcaster.Register(self.onJournalsClose,
            source='Journals', title='Closed')
        broadcaster.Register(self.onJournalModify,
            source='Journal', title='Modified')
        # The CalendarWindow broadcasts the following
        # to make us open the specified Day:
        broadcaster.Register(self.onCalendarDateSelect,
            source='Calendar', title='Date Selected')
        # broadcasted by MainView:    
        broadcaster.Register(self.onContactOpen,
            source='Contact', title='Opened')
    
    def createWidgets(self):
        "create the top level window"
        top = self.top = Toplevel(self.tkroot, class_='JournalWindow')
        top.protocol('WM_DELETE_WINDOW', self.close)
        top.title('Journal')
        top.iconname('PyCoCuMa')
        try:
            os.chdir(os.path.dirname(sys.argv[0]))
            if sys.platform == "win32":
                top.iconbitmap("pycocuma.ico")
            else:
                top.iconbitmap("@pycocuma.xbm")
                top.iconmask("@pycocuma_mask.xbm")
        except:
            debug.echo("Could not set TopLevel window icon")

        top.bind('<Control-s>', self.saveJournal)
        
        top.rowconfigure(0, weight=3)
        top.rowconfigure(1, weight=1)
        top.columnconfigure(0, weight=1)
    
        top.withdraw()

        import ToolTip
        import IconImages
        from InputWidgets import ArrowButton

        IconImages.createIconImages()
        
        self.lstJournal = self.JournalListWidget(top, self.model, self.openJournal)
        self.lstJournal.grid(row=0, columnspan=2, sticky=W+E+S+N)
        #self.lstJournal.textwidget().bind("<Button-3>", self.popup_menu, add="+")
        
        self.journaledit = self.JournalEditWidget(top)
        self.journaledit.grid(row=1, rowspan=2, column=0, sticky=W+E+S+N)
        
        self.btnbar = btnbar = Frame(top)
        self.btnNewJournal = Button(btnbar, image=IconImages.IconImages["newjournal"],
            command=self.newJournal)
        ToolTip.ToolTip(self.btnNewJournal, "Add New Journal Entry")
        self.btnNewJournal.pack()
        self.btnDelJournal = Button(btnbar, image=IconImages.IconImages["deljournal"],
            command=self.delJournal, state=DISABLED)
        ToolTip.ToolTip(self.btnDelJournal, "Delete this Journal Entry")
        self.btnDelJournal.pack()
        self.btnSaveJournal = Button(btnbar, image=IconImages.IconImages["savejournal"],
            command=self.saveJournal)
        ToolTip.ToolTip(self.btnSaveJournal, "Save this Journal Entry to server")
        self.btnSaveJournal.pack()
        self.btnFilterJournal = Button(btnbar, image=IconImages.IconImages["conntocard"],
            command=self.toggleConnectedToContact)
        ToolTip.ToolTip(self.btnFilterJournal, "Connect View to current Contact\n(show only journal entries with current contact as attendee)")
        self.btnFilterJournal.pack()
        btnbar.grid(row=1, column=1, sticky=N)
        
        self.btnHideJournalEdit = ArrowButton(top, direction='up',
            command=self.hideJournalEdit, width=24, height=10)
        self.btnHideJournalEdit.grid(row=2, column=1, sticky=W+E+S)    
        ToolTip.ToolTip(self.btnHideJournalEdit, "hide/show Journal Edit")
        
    popup = None
    def popup_menu(self, event=None):
        if not self.popup:
            self.popup = Toplevel(self.top)
            self.popup.withdraw()
            self.popup.wm_overrideredirect(1)
            self.popup.bind("<Leave>", self.popup_close)
            btn = Button(self.popup,
                text="Add New Journal Entry",
                command=self.newJournal)
            btn.pack()
        x = event.x_root
        y = event.y_root
        self.popup.geometry("+%d+%d" % (x, y))
        self.popup.deiconify()
        
    def popup_close(self, event=None):
        self.popup.withdraw()
        
    def centerWindow(self, relx=0.5, rely=0.3):
        "Center the Main Window on Screen"
        widget = self.top
        master = self.tkroot
        widget.update_idletasks() # Actualize geometry information
        if master.winfo_ismapped():
            m_width = master.winfo_width()
            m_height = master.winfo_height()
            m_x = master.winfo_rootx()
            m_y = master.winfo_rooty()
        else:
            m_width = master.winfo_screenwidth()
            m_height = master.winfo_screenheight()
            m_x = m_y = 0
        w_width = widget.winfo_reqwidth()
        w_height = widget.winfo_reqheight()
        x = m_x + (m_width - w_width) * relx
        y = m_y + (m_height - w_height) * rely
        if x+w_width > master.winfo_screenwidth():
            x = master.winfo_screenwidth() - w_width
        elif x < 0:
            x = 0
        if y+w_height > master.winfo_screenheight():
            y = master.winfo_screenheight() - w_height
        elif y < 0:
            y = 0
        widget.geometry("+%d+%d" % (x, y))
        widget.deiconify() # Become visible at the desired location
    
    def newJournal(self, initdict=None):
        "Add new (empty) journal"
        if initdict is None: initdict = {}
        initdict.setdefault('dtstart', time.strftime("%Y-%m-%d", time.gmtime()))
        initdict.setdefault('summary', '(no summary)')
        newhandle = self.model.NewJournal(initdict)
        if self._connectedToContact:
            # Add current Contact as Attendee:
            contact = broker.Request('Current Contact')
            import vcalendar
            event = vcalendar.vEvent()
            for key, val in zip(initdict.keys(), initdict.values()):
                getattr(event, key).set(val)
            event.attendee.append(vcalendar.vC_attendee())
            event.attendee[0].assignFromCard(contact)
            self.model.PutJournal(newhandle, event.VCF_repr())
        self.editJournal(newhandle)
    
    def askDelJournal(self, date, summary):
        "Display Dialog asking if user really wants to delete the journal entry"
        m = tkMessageBox.Message(
            title="Confirm Delete",
            message="Do You really want to delete the journal entry '%s'?" % (summary),
            icon=tkMessageBox.QUESTION,
            type=tkMessageBox.YESNO,
            master=self.top)
        return m.show()
        
    def delJournal(self):
        "Delete journal currently editing"
        date = self.journaledit.boundto().dtstart.get()
        summary = self.journaledit.boundto().summary.get()
        answer =  self.askDelJournal(date, summary)
        # Sometimes (esp. after Import) the answer is True
        # instead of 'yes': Why???
        if answer == 'yes' or answer == True:
            handle = self.journaledit.cardhandle()
            self.model.DelJournal(handle)
            self.journal_modified = 0
            self.openJournal()

    def askSaveJournal(self):
        "Display Dialog asking if user wants to save changes"
        m = tkMessageBox.Message(
            title="Save Changes?",
            message="This Journal has been modified.\nSave the Changes?",
            icon=tkMessageBox.QUESTION,
            type=tkMessageBox.YESNOCANCEL,
            master=self.top)
        return m.show()

    def saveJournal(self, event=None):
        "Upload the modified Journal to the Server"
        # To get the latest changes from all textedit widgets:
        self.journaledit.rebindWidgets()
        self.btnSaveJournal["state"] = DISABLED
        handle = self.journaledit.cardhandle() 
        if handle is None:
            handle = self.model.NewJournal()
        jour = self.journaledit.boundto() 
        if jour.summary.is_empty():
            jour.summary.set('(no summary)')
        self.model.PutJournal(handle, jour.VCF_repr())
        self.journal_modified = 0

    _connectedToContact = False
    def toggleConnectedToContact(self):
        if self._connectedToContact:
            self._connectedToContact = False
            self.btnFilterJournal["relief"] = RAISED
            self.lstJournal.applyFilter("All")
            self.openJournal()
        else:
            contact = broker.Request('Current Contact')
            uid = contact.uid
            self.lstJournal.applyFilter(uid.get())
            self._connectedToContact = True
            self.btnFilterJournal["relief"] = SUNKEN
            self.openJournal()
            if uid.is_empty():
                broadcaster.Broadcast('Notification', 'Warning', 
                    data={'message':"The current contact '%s' has no UID. " % contact.fn.get()+
                    "You must set the UID before you can connect the Journal to an contact."})

    def openJournal(self, handle=None):
        "Open journal by handle or default (first)"
        if self.journal_modified:
            # Sometimes (esp. after Import) the answer is True
            # instead of 'yes': Why???
            answer = self.askSaveJournal() 
            if answer == 'yes' or answer == True:
                self.saveJournal()
        if handle is None:
            handles = self.lstJournal.listFilteredHandles()
            if handles:
                journal = self.model.GetJournal(handles[-1])
            else:
                journal = None
        else:
            journal = self.model.GetJournal(handle)
        self.journaledit.bind_journal(journal)
        if journal:
            # Inform other widgets of newly opened journal:
            self.lstJournal.selectJournal(journal.handle())
            self.btnDelJournal["state"]=NORMAL
            # This is esp. for the CalendarWindow:
            broadcaster.Broadcast('Journal', 'Opened',
                data={'dtstart':journal.dtstart.get()})
        else:  
            self.btnDelJournal["state"]=DISABLED
        self.btnSaveJournal["state"]=DISABLED
        self.journal_modified = 0
    
    def viewJournal(self, handle=None):
        "Open the 'View Journal' Tab"
        self.openJournal(handle)
        
    def editJournal(self, handle=None):
        "Open the 'Edit Journal' Tab"
        self.openJournal(handle)
        self.journaledit.focus_set()
        
    def onJournalsOpen(self):
        "Callback, triggered on Broadcast"
        self.btnNewJournal["state"] = NORMAL
        self.btnDelJournal["state"] = NORMAL
        self.btnSaveJournal["state"] = DISABLED
        self.openJournal()
        
    def onJournalsClose(self):
        "Callback, triggered on Broadcast"
        self.btnNewJournal["state"] = DISABLED
        self.btnDelJournal["state"] = DISABLED
        self.btnSaveJournal["state"] = DISABLED
        
    journal_modified = 0
    def onJournalModify(self):
        "File was modified since last save"
        self.journal_modified = 1
        self.btnSaveJournal["state"] = NORMAL

    def onCalendarDateSelect(self):
        "A Day was selected in the CalendarWindow"
        date = broadcaster.CurrentData()['date']
        createnew = broadcaster.CurrentData().get('createnew')
        if createnew:
            self.newJournal(initdict={'dtstart':date})
        else:    
            handles = self.model.ListJournalHandles()
            # Calendar delivers date as YYYY-MM-DD, but dtstart may
            # include time, this means we cut off after 10th char:
            dates = map(lambda x: x[:10],
                self.model.QueryJournalAttributes(handles, 'DateStart'))
            try:
                idx = dates.index(date)
            except ValueError:
                return
            self.openJournal(handles[idx])
            
    def onContactOpen(self):        
        if self._connectedToContact:
            contact = broker.Request('Current Contact')
            self.lstJournal.applyFilter(contact.uid.get())
            self.openJournal()

    _journaledit_hidden = 0
    def hideJournalEdit(self):
        if self._journaledit_hidden:
            self.journaledit.grid()
            self.btnbar.grid()
            self._journaledit_hidden = 0
            self.btnHideJournalEdit.setdirection('up')
        else:   
            self.journaledit.grid_remove()
            self.btnbar.grid_remove()
            self._journaledit_hidden = 1
            self.btnHideJournalEdit.setdirection('down')
        
    def close(self, event=None):
        reply = 'none'
        if self.journal_modified:
            reply = self.askSaveJournal()
            if reply == 'yes' or reply == True:
                self.saveJournal()
        # We need str() here, because reply is a tcl_Obj:       
        if str(reply) != 'cancel':
            self._close()
        
    def _close(self):
        self.top.withdraw()
    
    def window(self):
        "Returns Tk's TopLevel Widget"
        return self.top
    
    def withdraw(self):
        "Withdraw: Forward to TopLevel Method"
        self.top.withdraw()
    
    def deiconify(self):
        "DeIconify: Forward to TopLevel Method"
        self.top.deiconify()

    def show(self):
        self.top.deiconify()
        self.top.lift()
        self.top.focus_set()
        
