#  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: 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('<FocusIn>', _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('<FocusIn>', _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("<Enter>", 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)

