# 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: ContactEditWidget.py 93 2004-11-28 16:05:34Z henning $ from Tkinter import * import Pmw import vcard import string import debug import IconImages import broadcaster import broker from AbstractContactView import * import ToolTip PADX = PADY = 1 ToolTips = { "pref": "Preferred", "home": "Home", "work": "Work", "intl": "International delivery address", "postal": "Postal delivery address", "parcel": "Parcel delivery address", "voice": "Voice phone", "cell": "Cellular phone", "pager": "Pager", "car": "Car-phone", "fax": "Facsimile (FAX)", "modem": "Modem connected", "msg": "Voice Messaging support", "isdn": "ISDN service telephone number", "video": "Video conferencing support"} # stores the handle of the contact under edit: affectedContact = None def _broadcast_contact_modify(): broadcaster.Broadcast('Contact', 'Modified', {'handle':affectedContact}, onceonly=1) currentEditControl = None def _setcurrentEditControl(event): global currentEditControl currentEditControl = event.widget import InputWidgets from InputWidgets import MultiRecordEdit class DateEdit(InputWidgets.DateEdit): def __init__(self, master, **kws): InputWidgets.DateEdit.__init__(self, master, **kws) self.add_save_hook(_broadcast_contact_modify) class MemoEdit(InputWidgets.MemoEdit): def __init__(self, master): InputWidgets.MemoEdit.__init__(self, master) self.add_save_hook(_broadcast_contact_modify) self.bind('', _setcurrentEditControl) class TextEdit(InputWidgets.TextEdit): def __init__(self, master): InputWidgets.TextEdit.__init__(self, master) self.add_save_hook(_broadcast_contact_modify) class MultiSelectButtons(InputWidgets.MultiSelectButtons): def __init__(self, master, buttondefs, icons, iconsgrey): InputWidgets.MultiSelectButtons.__init__(self, master, buttondefs, icons, iconsgrey) self.add_save_hook(_broadcast_contact_modify) class UTCOffsetPropEdit(InputWidgets.AbstractSingleVarEdit, Pmw.EntryField): import re _utcoffsetregex = re.compile('[-+][0-2][0-9]:[0-5][0-9]') def __init__(self, master, title, descr): InputWidgets.AbstractSingleVarEdit.__init__(self) Pmw.EntryField.__init__(self, master, entry_width = 10, entry_justify = LEFT, value = "+00:00", validate = {'validator' : self.validate}, modifiedcommand=self.save) ToolTip.ToolTip(self, descr) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('entry') def validate(self, str): if str: if not str[0] in "0123456789+-:": return 0 #ERROR elif self._utcoffsetregex.match(str) is None: return -1 #PARTIAL else: return 1 #OK else: return -1 #PARTIAL def get(self): return self.getvalue() # inherited from Pmw.EntryField def set(self, val): self.setvalue(val) class LatLongPropEdit(InputWidgets.AbstractSingleVarEdit, Frame): def __init__(self, master, title, descr): InputWidgets.AbstractSingleVarEdit.__init__(self) Frame.__init__(self, master) self.edtLat = Pmw.EntryField(self, entry_width = 8, entry_justify = LEFT, value = "0.0", validate = {'validator' : 'real', 'min':-90.0,'max':90.0}, modifiedcommand=self.save) ToolTip.ToolTip(self.edtLat.component('entry'), "Latitude as Float (53.5)") self.edtLat.grid(row=0, column=0) self.edtLon = Pmw.EntryField(self, entry_width = 8, entry_justify = LEFT, value = "0.0", validate = {'validator' : 'real', 'min':-90.0,'max':90.0}, modifiedcommand=self.save) ToolTip.ToolTip(self.edtLon.component('entry'), "Longitude as Float (10.0)") self.edtLon.grid(row=0, column=1) self.bind('', _setcurrentEditControl) def clear(self): self.edtLat.clear() self.edtLon.clear() def get(self): ret= str(self.edtLat.getvalue())+";"+str(self.edtLon.getvalue()) if ret==";": ret = "" return ret def set(self, val): parts = val.split(";") if len(parts)<2: parts = ["",""] self.edtLat.setvalue(parts[0]) self.edtLon.setvalue(parts[1]) class UIDControl(Frame): def __init__(self, master): Frame.__init__(self, master) self._contact = None self.__createWidgets() def __createWidgets(self): master = self master.columnconfigure(1, weight=1) label = Label(master, text="UID:") label.grid() self.__ctrUID = TextEdit(master) self.__ctrUID.grid(column=1, row=0, sticky=W+E) self._btnGenerate = Button(master, image=IconImages.IconImages["generate"], command=self._generateUID) self._btnGenerate.grid(column=2, row=0, sticky=W+E) ToolTip.ToolTip(self._btnGenerate, "generate new unique identifier") def _generateUID(self): "Generate Unique Identifier: xxxx-0000-00" # where xxxx is the fst part of the DisplayName # and 0000-00 are numbers from the md5-sum if self._contact: def isascii(c): return c in string.ascii_lowercase def isdigit(c): return c in string.digits import time, md5 dispname = self._contact.getDisplayName() alphastr = filter(isascii, dispname.lower()) # Alternative: time.strftime("%Y%m%d-%H%M%S") md = md5.new(alphastr+str(self._contact.handle())) sndalstr = filter(isascii, md.hexdigest().lower()) digitstr = filter(isdigit, md.hexdigest()) fstpart = alphastr[:4] pad = sndalstr[:(4-len(fstpart))] uidstr = fstpart+pad+"-"+digitstr[:4]+"-"+digitstr[-2:] self.__ctrUID.set(uidstr) def bindto(self, contact): self._contact = contact self.__ctrUID.bindto(contact.uid) class NameEdit(Pmw.Group): def __init__(self, master): Pmw.Group.__init__(self, master, tag_text = "Name") self.__createWidgets() def __createWidgets(self): master = self.interior() master.columnconfigure(1, weight=1) master.columnconfigure(3, weight=1) label = Label(master, text="Given:") label.grid(column=0,row=0) self.__edtGiven = TextEdit(master) self.__edtGiven.grid(column=1, row=0, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Middle:") label.grid(column=2,row=0) self.__edtMiddle = TextEdit(master) self.__edtMiddle.grid(column=3, row=0, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Family:") label.grid(column=0,row=1) self.__edtFamily = TextEdit(master) self.__edtFamily.grid(column=1, row=1, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Nick:") label.grid(column=2,row=1) self.__edtNick = TextEdit(master) self.__edtNick.grid(column=3, row=1, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Prefix:") label.grid(column=0,row=2) self.__edtPrefixes = TextEdit(master) self.__edtPrefixes.grid(column=1, row=2, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Suffix:") label.grid(column=2,row=2) self.__edtSuffixes = TextEdit(master) self.__edtSuffixes.grid(column=3, row=2, sticky=W+E, padx=PADX, pady=PADY) def getFormattedName(self): "constructs the FormattedName from the name components" parts = [] parts.append(self.__edtPrefixes.get()) parts.append(self.__edtGiven.get()) parts.append(self.__edtMiddle.get()) parts.append(self.__edtFamily.get()) parts.append(self.__edtSuffixes.get()) parts = filter(None, parts) return string.join(parts, " ") def fromFormattedName(self, fn): "tries to split name components" parts = map(string.strip, fn.split()) if len(parts) == 3: self.__edtGiven.set(parts[0]) self.__edtMiddle.set(parts[1]) self.__edtFamily.set(parts[2]) elif len(parts) == 2: self.__edtGiven.set(parts[0]) self.__edtFamily.set(parts[1]) elif len(parts) == 1: self.__edtGiven.set(parts[0]) self.__edtFamily.set(parts[1]) def bindto(self, n, nick): self.__edtGiven.bindto(n.given) self.__edtMiddle.bindto(n.additional) self.__edtFamily.bindto(n.family) self.__edtPrefixes.bindto(n.prefixes) self.__edtSuffixes.bindto(n.suffixes) self.__edtNick.bindto(nick) class AddressEdit(MultiRecordEdit): def __init__(self, master): MultiRecordEdit.__init__(self, master, vcard.vC_adr, "Address") def createBody(self): master = self.body master.columnconfigure(1, weight=1) master.columnconfigure(3, weight=1) label = Label(master, text="PO:") label.grid(column=0,row=1) self.__edtPostOffice = TextEdit(master) self.__edtPostOffice.grid(column=1, row=1, sticky=W+E, padx=PADX, pady=PADY) self.__edtPostOffice.component('entry').bind('<1>', self.onMouseClick) ToolTip.ToolTip(self.__edtPostOffice, "Post Office box") label = Label(master, text="Extd:") label.grid(column=2,row=1) self.__edtExtended = TextEdit(master) self.__edtExtended.grid(column=3, row=1, sticky=W+E, padx=PADX, pady=PADY) self.__edtExtended.component('entry').bind('<1>', self.onMouseClick) ToolTip.ToolTip(self.__edtExtended, "Extended address") label = Label(master, text="Street:") label.grid(column=0,row=2) self.__edtStreet = TextEdit(master) self.__edtStreet.grid(column=1, columnspan=3, row=2, sticky=W+E, padx=PADX, pady=PADY) self.__edtStreet.component('entry').bind('<1>', self.onMouseClick) label = Label(master, text="Code:") label.grid(column=0,row=3) self.__edtPostalCode = TextEdit(master) self.__edtPostalCode.grid(column=1, row=3, sticky=W+E, padx=PADX, pady=PADY) self.__edtPostalCode.component('entry').bind('<1>', self.onMouseClick) label = Label(master, text="City:") label.grid(column=2,row=3) self.__edtCity = TextEdit(master) self.__edtCity.grid(column=3, row=3, sticky=W+E, padx=PADX, pady=PADY) self.__edtCity.component('entry').bind('<1>', self.onMouseClick) label = Label(master, text="Region:") label.grid(column=0,row=4) self.__edtRegion = TextEdit(master) self.__edtRegion.grid(column=1, row=4, sticky=W+E, padx=PADX, pady=PADY) self.__edtRegion.component('entry').bind('<1>', self.onMouseClick) label = Label(master, text="Country:") label.grid(column=2,row=4) self.__edtCountry = TextEdit(master) self.__edtCountry.grid(column=3, row=4, sticky=W+E, padx=PADX, pady=PADY) self.__edtCountry.component('entry').bind('<1>', self.onMouseClick) label = Label(master, text="Type:") label.grid(column=0,row=5) btndefs = [] for key in vcard.vC_adr_types: if ToolTips.has_key(key): btndefs.append((key, ToolTips[key])) self.__selType = MultiSelectButtons(master, btndefs, IconImages.IconImages, IconImages.IconImagesGrey) self.__selType.grid(column=1, columnspan=3, row=5, sticky=W) def bodyChildren(self): return [\ self.__edtPostOffice, self.__edtExtended, self.__edtStreet, self.__edtPostalCode, self.__edtCity, self.__edtRegion, self.__edtCountry, self.__selType] def onMouseClick(self, event=None): if self.state == DISABLED: self._AddRecord() def onRecordAdd(self, rec): _broadcast_contact_modify() def onRecordDel(self): _broadcast_contact_modify() def bindtorec(self, rec): adr = rec if adr is not None: pobox = adr.pobox extended = adr.extended street = adr.street postcode = adr.postcode city = adr.city region = adr.region country = adr.country type = adr.params.get("type") else: pobox = None extended = None street = None postcode = None city = None region = None country = None type = None self.__edtPostOffice.bindto(pobox) self.__edtExtended.bindto(extended) self.__edtStreet.bindto(street) self.__edtPostalCode.bindto(postcode) self.__edtCity.bindto(city) self.__edtRegion.bindto(region) self.__edtCountry.bindto(country) self.__selType.bindto(type) class PhoneEdit(MultiRecordEdit): def __init__(self, master): MultiRecordEdit.__init__(self, master, vcard.vC_tel, "Telephone", "Phone") def createBody(self): master = self.body label = Label(master, text="Number:") label.grid(column=0,row=1) master.columnconfigure(1, weight=1) self.__edtNumber = TextEdit(master) self.__edtNumber.grid(column=1, row=1, sticky=W+E, padx=PADX, pady=PADY) self.__edtNumber.component('entry').bind('<1>', self.onMouseClick) label = Label(master, text="Type:") label.grid(column=0,row=5) btndefs = [] for key in vcard.vC_tel_types: if ToolTips.has_key(key): btndefs.append((key, ToolTips[key])) self.__selType = MultiSelectButtons(master, btndefs, IconImages.IconImages, IconImages.IconImagesGrey) self.__selType.grid(column=1, columnspan=2, row=5, sticky=W, padx=PADX, pady=PADY) def bodyChildren(self): return [\ self.__edtNumber, self.__selType] def onMouseClick(self, event=None): if self.state == DISABLED: self._AddRecord() def onRecordAdd(self, rec): _broadcast_contact_modify() def onRecordDel(self): _broadcast_contact_modify() def bindtorec(self, rec): if rec is not None: type = rec.params.get("type") else: type = None self.__edtNumber.bindto(rec) self.__selType.bindto(type) class EmailEdit(MultiRecordEdit): def __init__(self, master): MultiRecordEdit.__init__(self, master, vcard.vC_email, "Email") def createBody(self): master = self.body label = Label(master, text="Email:") label.grid(column=0,row=1) btndefs = [] for key in vcard.vC_email_types: if ToolTips.has_key(key): btndefs.append((key, ToolTips[key])) self.__selType = MultiSelectButtons(master, btndefs, IconImages.IconImages, IconImages.IconImagesGrey) self.__selType.grid(column=1, row=1, sticky=W, padx=PADX, pady=PADY) master.columnconfigure(2, weight=1) self.__edtEmail = TextEdit(master) self.__edtEmail.grid(column=2, row=1, sticky=W+E, padx=PADX, pady=PADY) self.__edtEmail.component('entry').bind('<1>', self.onMouseClick) def bodyChildren(self): return \ [self.__selType, self.__edtEmail] def onMouseClick(self, event=None): if self.state == DISABLED: self._AddRecord() def onRecordAdd(self, rec): _broadcast_contact_modify() def onRecordDel(self): _broadcast_contact_modify() def bindtorec(self, rec): if rec is not None: type = rec.params.get("type") else: type = None self.__edtEmail.bindto(rec) self.__selType.bindto(type) class OrganizationEdit(Pmw.Group): def __init__(self, master): Pmw.Group.__init__(self, master, tag_text = "Organization") self.__createWidgets() def __createWidgets(self): master = self.interior() master.columnconfigure(1, weight=1) master.columnconfigure(3, weight=1) label = Label(master, text="Name:") label.grid(column=0,row=0) self.__edtName = TextEdit(master) self.__edtName.grid(column=1, columnspan=3, row=0, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Units:") label.grid(column=0,row=1) self.__edtUnits = TextEdit(master) self.__edtUnits.grid(column=1, columnspan=3, row=1, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Title:") label.grid(column=0,row=2) self.__edtTitle = TextEdit(master) self.__edtTitle.grid(column=1, row=2, sticky=W+E, padx=PADX, pady=PADY) label = Label(master, text="Role:") label.grid(column=2,row=2) self.__edtRole = TextEdit(master) self.__edtRole.grid(column=3, row=2, sticky=W+E, padx=PADX, pady=PADY) def getOrganization(self): return self.__edtName.get() def bindto(self, org, title, role): if org is None: orgname = None orgunits = None else: orgname = org.org orgunits = org.units self.__edtName.bindto(orgname) self.__edtUnits.bindto(orgunits) self.__edtTitle.bindto(title) self.__edtRole.bindto(role) class NoteEdit(Pmw.Group): def __init__(self, master): Pmw.Group.__init__(self, master, tag_text = "Note") master = self.interior() self.edtNote = MemoEdit(master) master.columnconfigure(0, weight=1) master.rowconfigure(0, weight=1) self.edtNote.grid(sticky=W+E+S+N, padx=PADX, pady=PADY) def bindto(self, var): self.edtNote.bindto(var) class CategoriesEdit(Pmw.Group): def __init__(self, master): Pmw.Group.__init__(self, master, tag_text = "Categories") master = self.interior() self.edtCategories = TextEdit(master) master.columnconfigure(0, weight=1) self.edtCategories.grid(sticky=W+E, padx=PADX, pady=PADY) ToolTip.ToolTip(self.edtCategories, "list of categories separated by comma ','") def bindto(self, var): self.edtCategories.bindto(var) class URLEdit(Pmw.Group): def __init__(self, master): Pmw.Group.__init__(self, master, tag_text = "URL") master = self.interior() self.edtURL = TextEdit(master) self.edtURL.add_save_hook(self.updatestate) self.btnGotoURL = Button(master, command=self.gotoURL, image=IconImages.IconImages["webbrowser"], state=DISABLED) self.btnGotoURL.bind("", self.updatestate) master.columnconfigure(0, weight=1) master.rowconfigure(0, weight=1) self.edtURL.grid(sticky=W+E+S+N, padx=PADX, pady=PADY) self.btnGotoURL.grid(row=0, column=1, padx=PADX, pady=PADY) def gotoURL(self): import webbrowser webbrowser.open(self.edtURL.get(), 1) def updatestate(self, event=None): if self.edtURL.get(): self.btnGotoURL["state"] = NORMAL else: self.btnGotoURL["state"] = DISABLED def bindto(self, var): self.edtURL.bindto(var) self.updatestate() class ContactEditWidget(AbstractContactView, Frame): def __init__(self, master, **kws): AbstractContactView.__init__(self, **kws) Frame.__init__(self, master, class_="ContactEdit") self.propeditor = None self.__createWidgets() broker.Register("Current Contact", lambda self=self: self._contact) # Catch every Mouse-Click: self.bind_all('<1>', self.__EditControlSave) self.bind_all('<2>', self.__EditControlSave) self.bind_all('<3>', self.__EditControlSave) def __EditControlSave(self, event=None): if currentEditControl: try: currentEditControl.save() except: pass def bind_contact(self, contact): global affectedContact if contact: affectedContact = contact.handle() else: affectedContact = None AbstractContactView.bind_contact(self, contact) self.rebindWidgets() def getAddtlFields(self, contact): "Returns list of vCard field objects suitable for PropertyEditor" return [ contact.sort_string, contact.mailer, contact.key, contact.tz, contact.geo, contact.label, contact.photo, contact.logo] def rebindWidgets(self): self.edtFormattedName.bindto(self._contact.fn) self.edtBirthday.bindto(self._contact.bday) self.ctrUID.bindto(self._contact) self.edtName.bindto(self._contact.n, self._contact.nickname) self.edtOrganization.bindto(self._contact.org, self._contact.title, self._contact.role) self.edtAddress.bindto(self._contact.adr) self.edtPhone.bindto(self._contact.tel) self.edtEmail.bindto(self._contact.email) self.edtNote.bindto(self._contact.note) self.edtCategories.bindto(self._contact.categories) self.edtURL.bindto(self._contact.url) if self.propeditor: self.propeditor.bindto(self.getAddtlFields(self._contact)) def __takeFormattedNameFromName(self): self.edtFormattedName.clear() fn = self.edtName.getFormattedName() if not fn: fn = self.edtOrganization.getOrganization() self.edtFormattedName.set(fn) def _showAdditionalFields(self): import PropertyEditor if not self.propeditor: propdefs = [ ("Text", "Sort String", "contact will be sorted by this string"), ("Text", "Mailer Software", "user's electronic mail software"), ("Memo", "Key", "public PGP-key or similar"), ("UTCOffset", "Time Zone (+01:00)", "UTC offset"), ("LatLong", "Global Position", ""), ("Memo", "Address Label", "complete formatted postal address"), ("Image", "Photo", "Photographic picture of the contact"), ("Image", "Logo", "Company logo or similar"), ] self.propeditor = PropertyEditor.PropertyEditor(self, propdefs, title="Additional Fields", save_hook=_broadcast_contact_modify, editclasses={"UTCOffset":UTCOffsetPropEdit, "LatLong":LatLongPropEdit}) self.propeditor.bindto(self.getAddtlFields(self._contact)) self.propeditor.show() self.propeditor.lift() def __createWidgets(self): self.columnconfigure(0, weight=1) self.columnconfigure(3, weight=1) # Row 0: self.edtFormattedName = TextEdit(self) self.edtFormattedName.grid(sticky=W+E, padx=PADX, pady=PADY) ToolTip.ToolTip(self.edtFormattedName, "Formatted Name (card title), right-click: copy to name") self.btnTakeFromName = Button(self, image=IconImages.IconImages["assignfn"], command=self.__takeFormattedNameFromName) ToolTip.ToolTip(self.btnTakeFromName, "take from Name") self.btnTakeFromName.grid(column=1, row=0, padx=PADX, pady=PADY) self.edtBirthday = DateEdit(self, labelpos=W, label_text="Birthday:") self.edtBirthday.grid(column=2,row=0, padx=PADX, pady=PADY) ToolTip.ToolTip(self.edtBirthday, "birthday as ISO date (YYYY-MM-DD)") self.ctrUID = UIDControl(self) self.ctrUID.grid(column=3,row=0,sticky=W+E,padx=PADX,pady=PADY) self.btnAdditionalFields = Button(self, text="Additional Fields..", command=self._showAdditionalFields) self.btnAdditionalFields.grid(column=4, row=0, sticky=W+E,padx=PADX,pady=PADY) ToolTip.ToolTip(self.btnAdditionalFields, "show additional vCard fields") # Rows 1+2: self.edtName = NameEdit(self) def fnToName(event, self=self): self.edtName.fromFormattedName(self.edtFormattedName.get()) self.edtFormattedName.component("entry").bind("<3>", fnToName) self.edtName.grid(column=0, row=1, columnspan=3,sticky=W+E+S+N, padx=PADX, pady=PADY) self.edtOrganization = OrganizationEdit(self) self.edtOrganization.grid(column=0, row=2, columnspan=3,sticky=W+E+S+N, padx=PADX, pady=PADY) self.edtAddress = AddressEdit(self) self.edtAddress.grid(column=3,row=1,rowspan=2, columnspan=2, sticky=W+E+N+S, padx=PADX, pady=PADY) # Row 3: self.rowconfigure(3, weight=1) self.edtNote = NoteEdit(self) self.edtNote.grid(column=0, row=3, columnspan=3, rowspan=1, sticky=W+E+N+S, padx=PADX, pady=PADY) self.edtPhone = PhoneEdit(self) self.edtPhone.grid(column=3,row=3, columnspan=2, sticky=W+E+S+N, padx=PADX, pady=PADY) # Rows 4+5: self.edtCategories = CategoriesEdit(self) self.edtCategories.grid(column=0, row=4, columnspan=3, rowspan=1, sticky=W+E, padx=PADX, pady=PADY) self.edtURL = URLEdit(self) self.edtURL.grid(column=0, row=5, columnspan=3, rowspan=1, sticky=W+E+S, padx=PADX, pady=PADY) self.edtEmail = EmailEdit(self) self.edtEmail.grid(column=3,row=4, rowspan=2, columnspan=2, sticky=W+E+S+N, padx=PADX, pady=PADY)