""" Letter Composer for TeX-Letters """ # 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: LetterComposer.py 92 2004-11-28 15:34:44Z henning $ from Tkinter import * import Pmw import ToolTip import os, sys, types from InputWidgets import TextEdit, TextComboEdit, FilenameEdit, MemoEdit import Preferences import ConfigParser import __version__ PADX = PADY = 2 class Letter: "Encapsulates Letter-Data" def __init__(self): self._metasect = "MetaInfo" self._datasect = "LetterData" self._dict = { 'MySignature': "", 'MyPlace': "", 'MyAddress': "", 'MyBackAddress': "", 'MyBottomText': "", 'Date': "", 'Address': "", 'YourMail': "", 'Subject': "", 'Opening': "", 'Body': "", 'Closing': "", 'Enclosing': "" } def getDict(self): return self._dict def setDict(self, _dict): self._dict = _dict def saveToFile(self, filename): cp = ConfigParser.SafeConfigParser() # Make OptionNames case-sensitive: cp.optionxform = str # Add Meta-Info: sec = self._metasect if not cp.has_section(sec): cp.add_section(sec) import time cp.set(sec, "Creator", "PyCoCuMa-%s" % __version__.__version__) cp.set(sec, "CreationDate", time.strftime('%Y%m%dT%H%M%S')) # Add Data section: sec = self._datasect if not cp.has_section(sec): cp.add_section(sec) for key, val in zip(self._dict.keys(), self._dict.values()): cp.set(sec, key, val.encode('utf-8', 'replace')) cp.write(open(filename, 'wb')) def loadFromFile(self, filename): cp = ConfigParser.SafeConfigParser() # Make OptionNames case-sensitive: cp.optionxform = str cp.read([filename]) for opt in cp.options(self._datasect): self._dict[opt] = unicode(cp.get(self._datasect, opt), 'utf-8', 'replace') class LetterComposer(Pmw.MegaToplevel): def __init__(self, master): Pmw.MegaToplevel.__init__(self, master=master, title='Compose Letter') self.userdeletefunc(self.withdraw) self.master = master self.letter = Letter() self.createWidgets() self.fillDefaultValues() self.withdraw() def fillDefaultValues(self): "Get initial values from Preferences" myaddress = Preferences.get('lettercomposer.myaddress') self.edtMyAddress.clear() if myaddress: self.edtMyAddress.set(myaddress) templatefname = Preferences.get('lettercomposer.template_filename') if not templatefname: templatefname = os.path.dirname(sys.argv[0])+'/templates/DinBrief.tex' self.edtTemplateFilename.set(templatefname) self.edtSubject.clear() opening = Preferences.get('lettercomposer.opening', types.ListType) if opening is not None: self.edtOpening.setlist(opening) if len(opening)>0: self.edtOpening.set(opening[0]) self.edtBody.clear() closing = Preferences.get('lettercomposer.closing', types.ListType) if closing is not None: self.edtClosing.setlist(closing) if len(closing)>0: self.edtClosing.set(closing[0]) # Now fill the combobox lists: import time self.edtDate.setlist([ time.strftime('%d. %B %Y'), time.strftime('%x'), time.strftime('%Y-%m-%d')]) def createWidgets(self): hull = self.component('hull') hull.rowconfigure(0, weight=1) hull.columnconfigure(0, weight=1) top = Frame(hull, borderwidth=1, relief=RAISED) top.grid(sticky=W+E+S+N) sidepanel = Frame(hull, borderwidth=1, relief=RAISED) sidepanel.grid(row=0, column=1, sticky=N+S) self.edtTemplateFilename = FilenameEdit(sidepanel, showbasenameonly=True, filetypes=[('LaTeX Templates','*.tex'),('All Files','*')]) self.edtTemplateFilename.grid(sticky=W+E, padx=PADX, pady=PADY) self.edtTeXFilename = FilenameEdit(sidepanel, type='saveas', filetypes=[('LaTeX Files','*.tex'),('All Files','*')]) ToolTip.ToolTip(self.edtTeXFilename, 'TeX Filename to save to') self.edtTeXFilename.grid(sticky=W+E, padx=PADX, pady=PADY) btn = Button(sidepanel, text='Save Letter', command=self.saveLetterToFile) btn.grid(sticky=W+E, padx=PADX, pady=PADY) btn = Button(sidepanel, text='Load Letter', command=self.loadLetterFromFile) btn.grid(sticky=W+E, padx=PADX, pady=PADY) btn = Button(sidepanel, text='Save TeX', command=self.saveTeXToFile) btn.grid(sticky=W+E, padx=PADX, pady=PADY) btn = Button(sidepanel, text='Preview PDF', command=self.previewPdfFile) btn.grid(sticky=W+E, padx=PADX, pady=PADY) btn = Button(sidepanel, text='Close', command=self.close) btn.grid(sticky=W+E, padx=PADX, pady=PADY) self.use_signature = IntVar() self.use_signature.set(1) chk = Checkbutton(sidepanel, text='Use Signature', variable=self.use_signature) chk.grid(sticky=W+E, padx=PADX, pady=PADY) top.columnconfigure(0, weight=1) # Expand Body: top.rowconfigure(4, weight=1) self.edtAddress = MemoEdit(top, labelpos=N, label_text='Address to:') ToolTip.ToolTip(self.edtAddress, "recipient's address: exactly four lines are used") self.edtAddress.grid(sticky=W+E+S+N, padx=PADX, pady=PADY) self.edtMyAddress = MemoEdit(top, labelpos=N, label_text='My Address:') ToolTip.ToolTip(self.edtMyAddress, "sender's address: first line is also signature\n"+ 'first three lines are used as backaddress\n'+ 'you can specify as many lines as you need (e.g. for phone, email)') self.edtMyAddress.set(' ') self.edtMyAddress.tag_add('default', '0.0', END) self.edtMyAddress.tag_config('default', justify=RIGHT) self.edtMyAddress.grid(row=0, column=1, sticky=W+E+S+N, padx=PADX, pady=PADY) self.edtYourMail = TextEdit(top, labelpos=W, label_text='Your Mail:') ToolTip.ToolTip(self.edtYourMail, "refer to recipient's mail (usually by date)") self.edtYourMail.grid(sticky=W+E, padx=PADX, pady=PADY) self.edtDate = TextComboEdit(top, labelpos=W, label_text='Date:') ToolTip.ToolTip(self.edtDate, "leave empty to use today's date") self.edtDate.grid(row=1, column=1, sticky=W+E, padx=PADX, pady=PADY) self.edtSubject = TextEdit(top, labelpos=W, label_text='Subject:') self.edtSubject.grid(columnspan=2, sticky=W+E, padx=PADX, pady=PADY) self.edtOpening = TextComboEdit(top, labelpos=W, label_text='Opening:') self.edtOpening.grid(columnspan=2, sticky=W+E, padx=PADX, pady=PADY) self.edtBody = MemoEdit(top, text_height=8, file_extension='.tex') ToolTip.ToolTip(self.edtBody, 'text body: you can use LaTeX commands here\n' + '(Press CTRL-E to open external editor)') self.edtBody.grid(columnspan=2, sticky=W+E+S+N, padx=PADX, pady=PADY) self.edtClosing = TextComboEdit(top, labelpos=W, label_text='Closing:') self.edtClosing.grid(columnspan=2, sticky=W+E, padx=PADX, pady=PADY) self.edtEnclosing = TextEdit(top, labelpos=W, label_text='Enclosing:') ToolTip.ToolTip(self.edtEnclosing, 'list of attachments, separate by LaTeX linebreak (\\\\)') self.edtEnclosing.grid(columnspan=2, sticky=W+E, padx=PADX, pady=PADY) def createLetterFromWidgets(self): address_lines = self.edtAddress.get().splitlines() # We want exactly four lines: for i in range(4-len(address_lines)): address_lines.append('') myaddress_lines = self.edtMyAddress.get().splitlines() if self.use_signature.get() and len(myaddress_lines)>0: signature = myaddress_lines[0] else: signature = '' letterdict = { 'MySignature':signature, 'MyPlace':'', 'MyAddress':"\n".join(myaddress_lines), 'MyBackAddress':"\n".join(myaddress_lines[:3]), 'MyBottomText':'', 'Date':self.edtDate.get(), 'Address':"\n".join(address_lines), 'YourMail':self.edtYourMail.get(), 'Subject':self.edtSubject.get(), 'Opening':self.edtOpening.get(), 'Body':self.edtBody.get(), 'Closing':self.edtClosing.get(), 'Enclosing':self.edtEnclosing.get() } self.letter.setDict(letterdict) def saveLetterToFile(self): self.createLetterFromWidgets() import tkFileDialog dlg = tkFileDialog.SaveAs(self.master, filetypes=[("PyCoCuMa Letter Files", "*.letter")]) letter_dir = Preferences.get('lettercomposer.letter_dir') letter_fname, file_ext = os.path.splitext(os.path.basename(self.edtTeXFilename.get())) letter_fname += '.letter' fname = dlg.show(initialdir=letter_dir, initialfile=letter_fname) if fname: self.letter.saveToFile(fname) def loadLetterFromFile(self): import tkFileDialog dlg = tkFileDialog.Open(self.master, filetypes=[("PyCoCuMa Letter Files", "*.letter"), ("All Files", "*")]) letter_dir = Preferences.get('lettercomposer.letter_dir') fname = dlg.show(initialdir=letter_dir) if fname: self.letter.loadFromFile(fname) d = self.letter.getDict() self.edtMyAddress.set(d["MyAddress"]) self.edtDate.set(d["Date"]) self.edtAddress.set(d["Address"]) self.edtYourMail.set(d["YourMail"]) self.edtSubject.set(d["Subject"]) self.edtOpening.set(d["Opening"]) self.edtBody.set(d["Body"]) self.edtClosing.set(d["Closing"]) self.edtEnclosing.set(d["Enclosing"]) def saveTeXToFile(self): self.createLetterFromWidgets() def enclatin1(str): return str.encode('latin-1', 'replace') from TemplateProcessor import TemplateProcessor proc = TemplateProcessor(postproc=enclatin1) outfile = file(self.edtTeXFilename.get(), 'wb') d = self.letter.getDict().copy() d["MyAddress"] = d["MyAddress"].splitlines() d["MyBackAddress"] = d["MyBackAddress"].splitlines() d["Address"] = d["Address"].splitlines() proc.process(file(self.edtTemplateFilename.get()).readlines(), outfile.write, d) outfile.close() self.savePreferences() def savePreferences(self): Preferences.set('lettercomposer.myaddress', self.edtMyAddress.get()) opening = self.edtOpening.get() openings = list(self.edtOpening.getlist()) if opening and not opening in openings: openings.insert(0, opening) Preferences.set('lettercomposer.opening', openings[:10]) closing = self.edtClosing.get() closings = list(self.edtClosing.getlist()) if closing and not closing in closings: closings.insert(0, closing) Preferences.set('lettercomposer.closing', closings[:10]) Preferences.set('lettercomposer.template_filename', self.edtTemplateFilename.get()) Preferences.set('lettercomposer.letter_dir', os.path.dirname(self.edtTeXFilename.get())) def previewPdfFile(self): import texwrapper texfilename = self.edtTeXFilename.get() if not os.access(texfilename, os.F_OK): import tkMessageBox tkMessageBox.showerror("Error", "File not found: '%s'\n You must save the TeX-file first!" % texfilename) return texwrapper.run_pdflatex(texfilename) texwrapper.view_pdf(texfilename) def close(self): self.withdraw() def composeTo(self, addressee, adr): self.fillDefaultValues() adrstreet = adr.street.get() adrcity = '%s %s' % (adr.postcode.get(), adr.city.get()) address = '\n%s\n%s\n%s' % (addressee.fn.get(), adrstreet, adrcity) self.edtAddress.clear() self.edtAddress.set(address) texfname = addressee.uid.get() if not texfname: texfname = addressee.fn.get().replace(' ', '_') texdir = Preferences.get('lettercomposer.letter_dir') if not texdir: texdir = os.path.dirname(sys.argv[0]) import time texfname = '%s/%s-%s.tex' % (texdir, texfname, time.strftime('%Y%m%d')) self.edtTeXFilename.set(texfname) self.show() _firstshow = 1 def show(self): if self._firstshow: self.centerWindow() else: self.deiconify() self.lift() self._firstshow = 0 def centerWindow(self, relx=0.5, rely=0.3): "Center the Window on Screen" widget = self master = self.master 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 if __name__ == "__main__": import vcard import testvcard tk = Tk() dlg = LetterComposer(tk) card = vcard.vCard() testvcard.fillDemoCard(card) dlg.composeTo(card, card.adr[0]) tk.mainloop()