""" Journal Toplevel Window """ # Copyright (C) 2004 Henning Jacobs # # 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('', 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("", 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("", 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()