PyXR

c:\projects\bitpim\src \ phonebookentryeditor.py



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: phonebookentryeditor.py 4693 2008-08-15 22:30:51Z djpham $
0009 
0010 from __future__ import with_statement
0011 import wx
0012 
0013 import fixedscrolledpanel
0014 import pubsub
0015 import bphtml
0016 import database
0017 import nameparser
0018 import wallpaper
0019 import guihelper
0020 import field_color
0021 
0022 """The dialog for editing a phonebook entry"""
0023 
0024 # global ringone & wallpaper list
0025 _ringtone_list=None
0026 _wallpaper_list=None
0027 
0028 # NavToolBar--------------------------------------------------------------------
0029 class NavToolBar(wx.ToolBar):
0030     _id_up=wx.NewId()
0031     _id_down=wx.NewId()
0032     _id_del=wx.NewId()
0033     def __init__(self, parent, horizontal=True):
0034         self._parent=parent
0035         self._grandpa=parent.GetParent()
0036         _style=wx.TB_FLAT
0037         if horizontal:
0038             _style|=wx.TB_HORIZONTAL
0039         else:
0040             _style|=wx.TB_VERTICAL
0041         super(NavToolBar, self).__init__(parent, -1, style=_style)
0042         self.SetToolBitmapSize(wx.Size(16, 16))
0043         sz=self.GetToolBitmapSize()
0044         self.AddLabelTool(NavToolBar._id_up, "Up", wx.ArtProvider.GetBitmap(guihelper.ART_ARROW_UP, wx.ART_TOOLBAR, sz), shortHelp="Move field up")
0045         self.AddLabelTool(NavToolBar._id_down, "Down", wx.ArtProvider.GetBitmap(guihelper.ART_ARROW_DOWN, wx.ART_TOOLBAR, sz), shortHelp="Move field down")
0046         self.AddLabelTool(NavToolBar._id_del, "Delete", wx.ArtProvider.GetBitmap(guihelper.ART_DEL_FIELD, wx.ART_TOOLBAR, sz), shortHelp="Delete field")
0047         if hasattr(self._grandpa, 'MoveField'):
0048             wx.EVT_TOOL(self, NavToolBar._id_up, self.OnMoveUp)
0049             wx.EVT_TOOL(self, NavToolBar._id_down, self.OnMoveDown)
0050         if hasattr(self._grandpa, 'DeleteField'):
0051             wx.EVT_TOOL(self, NavToolBar._id_del, self.OnDelete)
0052         self.Realize()
0053 
0054     def OnMoveUp(self, _):
0055         self._grandpa.MoveField(self._parent, -1)
0056 
0057     def OnMoveDown(self, _):
0058         self._grandpa.MoveField(self._parent, +1)
0059 
0060     def OnDelete(self, _):
0061         self._grandpa.DeleteField(self._parent)
0062 
0063 # DirtyUIBase-------------------------------------------------------------------
0064 myEVT_DIRTY_UI=wx.NewEventType()
0065 EVT_DIRTY_UI=wx.PyEventBinder(myEVT_DIRTY_UI, 1)
0066 
0067 class DirtyUIBase(wx.Panel):
0068     """ Base class to add the capability to generate a DirtyUI event"""
0069     def __init__(self, parent):
0070         wx.Panel.__init__(self, parent, -1)
0071         self.dirty=self.ignore_dirty=False
0072 
0073     def OnDirtyUI(self, evt):
0074         if self.dirty or self.ignore_dirty:
0075             return
0076         self.dirty=True
0077         self.GetEventHandler().ProcessEvent(\
0078             wx.PyCommandEvent(myEVT_DIRTY_UI, self.GetId()))
0079     def Clean(self):
0080         self.dirty=False
0081         self.ignore_dirty=False
0082     def Ignore(self, ignore=True):
0083         self.ignore_dirty=ignore
0084     def Enable(self, enable=True):
0085         super(DirtyUIBase, self).Enable(enable)
0086         self.Refresh()
0087 
0088 # My ListBox class--------------------------------------------------------------
0089 class ListBox(wx.ListBox):
0090     """BitPim ListBox class that caches the selection string necessary for this
0091     implementation.
0092     """
0093     def __init__(self, *args, **kwargs):
0094         super(ListBox, self).__init__(*args,  **kwargs)
0095         self._selstr=''
0096         wx.EVT_LISTBOX(self, self.GetId(), self._OnSelected)
0097     def _OnSelected(self, evt):
0098         self._selstr=evt.GetString()
0099         evt.Skip()
0100     def GetStringSelection(self):
0101         return self._selstr
0102     def SetStringSelection(self, selection):
0103         try:
0104             super(ListBox, self).SetStringSelection(selection)
0105             self._selstr=selection
0106         except:
0107             self._selstr=''
0108     def SetSelection(self, idx):
0109         try:
0110             super(ListBox, self).SetSelection(idx)
0111             self._selstr=self.GetString(idx)
0112         except:
0113             self._selstr=''
0114 
0115 # RingtoneEditor----------------------------------------------------------------
0116 class MediaPreviewWindow(bphtml.HTMLWindow):
0117     """A subclass of BitPim HTMLWindow that launches a media item when clicked"""
0118     def OnLinkClicked(self, evt):
0119         pubsub.publish(pubsub.REQUEST_MEDIA_OPEN,
0120                        (evt.GetHref(), None))
0121 
0122 class RingtoneEditor(DirtyUIBase):
0123     "Edit a ringtone"
0124 
0125     # this is almost an exact clone of the wallpaper editor
0126     
0127     unnamed="Select:"
0128     unknownselprefix=": "
0129 
0130     choices=["call", "message", "calendar"]
0131 
0132     ID_LIST=wx.NewId()
0133 
0134     _bordersize=3
0135 
0136     def __init__(self, parent, _, has_type=True, navtoolbar=False):
0137         DirtyUIBase.__init__(self, parent)
0138 
0139         _box=field_color.build_color_field(self, wx.StaticBox,
0140                                            (self, -1, "Ringtone"), 'ringtone')
0141         hs=wx.StaticBoxSizer(_box, wx.HORIZONTAL)
0142         self.static_box=_box
0143         vs=wx.BoxSizer(wx.VERTICAL)
0144 
0145         self.preview=MediaPreviewWindow(self, -1)
0146         self.preview.SetBorders(self._bordersize)
0147         vs.Add(self.preview, 1, wx.EXPAND|wx.ALL, 5)
0148         self.type=wx.ComboBox(self, -1, "call", choices=self.choices, style=wx.CB_READONLY)
0149         self.type.SetSelection(0)
0150         vs.Add(self.type, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0151         # Hide the 'type' combo box if not requested
0152         if not has_type:
0153             vs.Hide(1)
0154 
0155         hs.Add(vs, 1, wx.EXPAND|wx.ALL, 5)
0156 
0157         self.ringtone=ListBox(self, self.ID_LIST, choices=[self.unnamed], size=(-1,200))
0158         hs.Add(self.ringtone, 1, wx.EXPAND|wx.ALL, 5)
0159         if navtoolbar:
0160             hs.Add(NavToolBar(self, False), 0, wx.EXPAND|wx.BOTTOM, 5)
0161         self.SetSizer(hs)
0162         hs.Fit(self)
0163 
0164         pubsub.subscribe(self.OnRingtoneUpdates, pubsub.ALL_RINGTONES)
0165         wx.CallAfter(pubsub.publish, pubsub.REQUEST_RINGTONES) # make the call once we are onscreen
0166         pubsub.subscribe(self.OnPreviewUpdate, pubsub.RESPONSE_MEDIA_INFO)
0167 
0168         wx.EVT_LISTBOX(self, self.ID_LIST, self.OnLBClicked)
0169         wx.EVT_LISTBOX_DCLICK(self, self.ID_LIST, self.OnLBClicked)
0170 
0171     def __del__(self):
0172         pubsub.unsubscribe(self.OnRingtoneUpdates)
0173         pubsub.unsubscribe(self.OnPreviewUpdate)
0174         super(RingtoneEditor, self).__del__()
0175 
0176     def OnRingtoneUpdates(self, msg):
0177         # wxPython/wxWidget bug: ListBox.Clear emits a wx.EVT_LISTBOX event
0178         # it shouldn't
0179         self.Ignore()
0180         tones=msg.data[:]
0181         cur=self._get()
0182         self.ringtone.Clear()
0183         self.ringtone.Append(self.unnamed)
0184         for p in tones:
0185             self.ringtone.Append(p)
0186         self._set(cur)
0187         self.Clean()
0188 
0189     def OnLBClicked(self, evt=None):
0190         if self.ringtone.GetSelection()==wx.NOT_FOUND:
0191             return
0192         self.OnDirtyUI(evt)
0193         self._updaterequested=False
0194         v=self._get().get('ringtone', None)
0195         self.SetPreview(v)
0196 
0197     _preview_html='<img src="bpimage:ringer.png;width=24;height=24"><P>%s'
0198     def OnPreviewUpdate(self, msg):
0199         # Media tab replies with some description about the selected media item
0200         if msg.data['client'] is self:
0201             # this one's for moi!
0202             if msg.data['canopen']:
0203                 _s='<A HREF="%s">%s</A><BR>'%(msg.data['desc'][0], msg.data['desc'][0]) +\
0204                     '<BR>'.join(msg.data['desc'][1:])
0205             else:
0206                 _s='<BR>'.join(msg.data['desc'])
0207             self.preview.SetPage(self._preview_html%_s)
0208 
0209     def SetPreview(self, name):
0210         if name is None or name==self.unnamed:
0211             self.preview.SetPage('')
0212         else:
0213             self.preview.SetPage(self._preview_html%name)
0214             pubsub.publish(pubsub.REQUEST_MEDIA_INFO, (self, name, None))
0215 
0216     def _set(self, data):
0217         if data is None:
0218             wp=self.unnamed
0219             type='call'
0220         else:
0221             wp=data.get("ringtone", self.unnamed)
0222             type=data.get("use", "call")
0223 
0224         self.SetPreview(wp)
0225         if type=='calendar':
0226             self.type.SetSelection(2)
0227         elif type=="message":
0228             self.type.SetSelection(1)
0229         else:
0230             self.type.SetSelection(0)
0231 
0232         # zero len?
0233         if len(wp)==0:
0234             self.ringtone.SetSelection(0)
0235             return
0236 
0237         # try using straight forward name
0238         try:
0239             self.ringtone.SetStringSelection(wp)
0240             return
0241         except:
0242             pass
0243 
0244         # ok, with unknownselprefix
0245         try:
0246             self.ringtone.SetStringSelection(self.unknownselprefix+wp)
0247             return
0248         except:
0249             pass
0250 
0251         # ok, just add it
0252         self.ringtone.InsertItems([self.unknownselprefix+wp], 1)
0253         self.ringtone.SetStringSelection(self.unknownselprefix+wp)
0254 
0255     def Set(self, data):
0256         self.Ignore(True)
0257         self._set(data)
0258         self.Clean()
0259 
0260     def _get(self):
0261         res={}
0262         rt=self.ringtone.GetStringSelection()
0263         if rt==self.unnamed:
0264             return res
0265         if rt.startswith(self.unknownselprefix):
0266             rt=rt[len(self.unknownselprefix):]
0267         if len(rt):
0268             res['ringtone']=rt
0269             res['use']=self.type.GetStringSelection()
0270         return res
0271         
0272     def Get(self):
0273         self.Clean()
0274         return self._get()
0275         
0276 # WallpaperEditor---------------------------------------------------------------
0277 class WallpaperEditor(DirtyUIBase):
0278 
0279     unnamed="Select:"
0280     unknownselprefix=": "
0281 
0282     choices=["call", "message", "calendar"]
0283 
0284     ID_LIST=wx.NewId()
0285 
0286     _bordersize=3 # border inside HTML widget
0287     
0288     def __init__(self, parent, _, has_type=True, navtoolbar=False):
0289         DirtyUIBase.__init__(self, parent)
0290 
0291         _box=field_color.build_color_field(self, wx.StaticBox,
0292                                            (self, -1, "Wallpaper"),
0293                                            'wallpaper')
0294         hs=wx.StaticBoxSizer(_box, wx.HORIZONTAL)
0295         self.static_box=_box
0296 
0297         vs=wx.BoxSizer(wx.VERTICAL)
0298 
0299         self.preview=wallpaper.WallpaperPreview(self)
0300         self.type=wx.ComboBox(self, -1, "call", choices=self.choices, style=wx.CB_READONLY)
0301         self.type.SetSelection(0)
0302         vs.Add(self.preview, 1, wx.EXPAND|wx.ALL, 5)
0303         vs.Add(self.type, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0304         # Hide the 'type' combo box if not requested
0305         if not has_type:
0306             vs.Hide(1)
0307 
0308         hs.Add(vs, 1, wx.EXPAND|wx.ALL, 5)
0309 
0310         self.wallpaper=ListBox(self, self.ID_LIST, choices=[self.unnamed], size=(-1,200), style=wx.LB_SINGLE)
0311         hs.Add(self.wallpaper, 1, wx.EXPAND|wx.ALL, 5)
0312         if navtoolbar:
0313             hs.Add(NavToolBar(self, False), 0, wx.EXPAND|wx.BOTTOM, 5)
0314         self.SetSizer(hs)
0315         hs.Fit(self)
0316 
0317         pubsub.subscribe(self.OnWallpaperUpdates, pubsub.ALL_WALLPAPERS)
0318         wx.CallAfter(pubsub.publish, pubsub.REQUEST_WALLPAPERS) # make the call once we are onscreen
0319 
0320         wx.EVT_LISTBOX(self, self.ID_LIST, self.OnLBClicked)
0321         wx.EVT_LISTBOX_DCLICK(self, self.ID_LIST, self.OnLBClicked)
0322 
0323     def __del__(self):
0324         pubsub.unsubscribe(self.OnWallpaperUpdates)
0325         super(WallpaperEditor, self).__del__()
0326 
0327     def OnWallpaperUpdates(self, msg):
0328         "Receives pubsub message with wallpaper list"
0329         # wxPython/wxWidget bug: ListBox.Clear emits a wx.EVT_LISTBOX event
0330         # it shouldn't
0331         self.Ignore()
0332         papers=msg.data[:]
0333         cur=self._get()
0334         self.wallpaper.Clear()
0335         self.wallpaper.Append(self.unnamed)
0336         for p in papers:
0337             self.wallpaper.Append(p)
0338         self._set(cur)
0339         self.Clean()
0340 
0341     def OnLBClicked(self, evt=None):
0342         if self.wallpaper.GetSelection()==wx.NOT_FOUND:
0343             return
0344         self.OnDirtyUI(evt)
0345         v=self.Get().get('wallpaper', None)
0346         self.SetPreview(v)
0347 
0348     def SetPreview(self, name):
0349         if name is None or name is self.unnamed:
0350             self.preview.SetImage(None)
0351         else:
0352             self.preview.SetImage(name)        
0353 
0354     def _set(self, data):
0355         if data is None:
0356             wp=self.unnamed
0357             type='call'
0358         else:
0359             wp=data.get("wallpaper", self.unnamed)
0360             type=data.get("use", "call")
0361 
0362         self.SetPreview(wp)
0363         if type=="message":
0364             self.type.SetSelection(1)
0365         elif type=='calendar':
0366             self.type.SetSelection(2)
0367         else:
0368             self.type.SetSelection(0)
0369 
0370         if len(wp)==0:
0371             self.wallpaper.SetSelection(0)
0372             return
0373 
0374         # try using straight forward name
0375         try:
0376             self.wallpaper.SetStringSelection(wp)
0377             return
0378         except:
0379             pass
0380 
0381         # ok, with unknownselprefix
0382         try:
0383             self.wallpaper.SetStringSelection(self.unknownselprefix+wp)
0384             return
0385         except:
0386             pass
0387 
0388         # ok, just add it
0389         self.wallpaper.InsertItems([self.unknownselprefix+wp], 1)
0390         self.wallpaper.SetStringSelection(self.unknownselprefix+wp)
0391 
0392     def Set(self, data):
0393         self.Ignore()
0394         self._set(data)
0395         self.Clean()
0396 
0397     def _get(self):
0398         res={}
0399         wp=self.wallpaper.GetStringSelection()
0400         if wp==self.unnamed:
0401             return res
0402         if wp.startswith(self.unknownselprefix):
0403             wp=wp[len(self.unknownselprefix):]
0404         if len(wp):
0405             res['wallpaper']=wp
0406             res['use']=self.type.GetStringSelection()
0407         return res
0408 
0409     def Get(self):
0410         self.Clean()
0411         return self._get()
0412 
0413 # CategoryManager---------------------------------------------------------------
0414 class CategoryManager(wx.Dialog):
0415 
0416     def __init__(self, parent, title="Manage Categories"):
0417         wx.Dialog.__init__(self, parent, -1, title, style=wx.CAPTION|wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|
0418                            wx.RESIZE_BORDER)
0419 
0420         vs=wx.BoxSizer(wx.VERTICAL)
0421         hs=wx.BoxSizer(wx.HORIZONTAL)
0422         self.delbut=wx.Button(self, wx.NewId(), "Delete")
0423         self.addbut=wx.Button(self, wx.NewId(), "Add")
0424         self.add=wx.TextCtrl(self, -1)
0425         hs.Add(self.delbut,0, wx.EXPAND|wx.ALL, 5)
0426         hs.Add(self.addbut,0, wx.EXPAND|wx.ALL, 5)
0427         hs.Add(self.add, 1, wx.EXPAND|wx.ALL, 5)
0428         vs.Add(hs, 0, wx.EXPAND|wx.ALL, 5)
0429 
0430         self.thelistb=wx.ListBox(self, -1, size=(100, 250), style=wx.LB_SORT)
0431         self.addlistb=wx.ListBox(self, -1, style=wx.LB_SORT)
0432         self.dellistb=wx.ListBox(self, -1, style=wx.LB_SORT)
0433 
0434         hs=wx.BoxSizer(wx.HORIZONTAL)
0435 
0436         vs2=wx.BoxSizer(wx.VERTICAL)
0437         vs2.Add(wx.StaticText(self, -1, "  List"), 0, wx.ALL, 2)
0438         vs2.Add(self.thelistb, 1, wx.ALL|wx.EXPAND, 5)
0439         hs.Add(vs2, 1, wx.ALL|wx.EXPAND, 5)
0440 
0441         vs2=wx.BoxSizer(wx.VERTICAL)
0442         vs2.Add(wx.StaticText(self, -1, "  Added"), 0, wx.ALL, 2)
0443         vs2.Add(self.addlistb, 1, wx.ALL|wx.EXPAND, 5)
0444         hs.Add(vs2, 1, wx.ALL|wx.EXPAND, 5)
0445 
0446         vs2=wx.BoxSizer(wx.VERTICAL)
0447         vs2.Add(wx.StaticText(self, -1, "  Deleted"), 0, wx.ALL, 2)
0448         vs2.Add(self.dellistb, 1, wx.ALL|wx.EXPAND, 5)
0449         hs.Add(vs2, 1, wx.ALL|wx.EXPAND, 5)
0450 
0451         vs.Add(hs, 1, wx.EXPAND|wx.ALL, 5)
0452         vs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
0453         vs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0454 
0455         self.SetSizer(vs)
0456         vs.Fit(self)
0457 
0458         self.curlist=None
0459         self.dellist=[]
0460         self.addlist=[]
0461 
0462         pubsub.subscribe(self.OnUpdateCategories, pubsub.ALL_CATEGORIES)
0463         pubsub.publish(pubsub.REQUEST_CATEGORIES)
0464 
0465         wx.EVT_BUTTON(self, self.addbut.GetId(), self.OnAdd)
0466         wx.EVT_BUTTON(self, self.delbut.GetId(), self.OnDelete)
0467         wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
0468         wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnCancel)
0469 
0470     def __del__(self):
0471         pubsub.unsubscribe(self.OnUpdateCategories)
0472         super(CategoryManager, self).__del__()
0473 
0474     def OnUpdateCategories(self, msg):
0475         cats=msg.data[:]
0476         if self.curlist is None:
0477             self.curlist=cats
0478 
0479         # add in any new entries that may have appeared
0480         for i in cats:
0481             if i not in self.curlist and i not in self.dellist:
0482                 self.curlist.append(i)
0483                 self.addlist.append(i)
0484         self.curlist.sort()
0485         self.addlist.sort()
0486         self.UpdateLBs()
0487 
0488     def UpdateLBs(self):
0489         for lb,l in (self.thelistb, self.curlist), (self.addlistb, self.addlist), (self.dellistb, self.dellist):
0490             lb.Clear()
0491             for i in l:
0492                 lb.Append(i)
0493         
0494     def OnOk(self, _):
0495         pubsub.publish(pubsub.SET_CATEGORIES, self.curlist)
0496         self.Show(False)
0497         self.Destroy()
0498 
0499     def OnCancel(self, _):
0500         self.Show(False)
0501         self.Destroy()
0502         
0503     def OnAdd(self, _):
0504         v=self.add.GetValue()
0505         self.add.SetValue("")
0506         self.add.SetFocus()
0507         if len(v)==0:
0508             return
0509         if v not in self.curlist:
0510             self.curlist.append(v)
0511             self.curlist.sort()
0512         if v not in self.addlist:
0513             self.addlist.append(v)
0514             self.addlist.sort()
0515         if v in self.dellist:
0516             i=self.dellist.index(v)
0517             del self.dellist[i]
0518         self.UpdateLBs()
0519 
0520     def OnDelete(self,_):
0521         try:
0522             v=self.thelistb.GetStringSelection()
0523             if v is None or len(v)==0: return
0524         except:
0525             return
0526         i=self.curlist.index(v)
0527         del self.curlist[i]
0528         if v in self.addlist:
0529             i=self.addlist.index(v)
0530             del self.addlist[i]
0531         self.dellist.append(v)
0532         self.dellist.sort()
0533         self.UpdateLBs()
0534                
0535 # CategoryEditor----------------------------------------------------------------
0536 class CategoryEditor(DirtyUIBase):
0537 
0538     # we have to have an entry with a special string for the unnamed string
0539 
0540     unnamed="Select:"
0541 
0542     def __init__(self, parent, pos, navtoolbar=False):
0543         DirtyUIBase.__init__(self, parent)
0544         _box=field_color.build_color_field(self, wx.StaticBox,
0545                                            (self, -1, "Category"),
0546                                            'category')
0547         hs=wx.StaticBoxSizer(_box, wx.HORIZONTAL)
0548 
0549         self.categories=[self.unnamed]
0550         self.category=wx.ListBox(self, -1, choices=self.categories)
0551         pubsub.subscribe(self.OnUpdateCategories, pubsub.ALL_CATEGORIES)
0552         pubsub.publish(pubsub.REQUEST_CATEGORIES)
0553         hs.Add(self.category, 1, wx.EXPAND|wx.ALL, 5)
0554         
0555         if pos==0:
0556             self.but=wx.Button(self, wx.NewId(), "Manage Categories")
0557             hs.Add(self.but, 2, wx.ALIGN_CENTRE|wx.ALL, 5)
0558             wx.EVT_BUTTON(self, self.but.GetId(), self.OnManageCategories)
0559         else:
0560             hs.Add(wx.StaticText(self, -1, ""), 2, wx.ALIGN_CENTRE|wx.ALL, 5)
0561 
0562         wx.EVT_LISTBOX(self, self.category.GetId(), self.OnDirtyUI)
0563         wx.EVT_LISTBOX_DCLICK(self, self.category.GetId(), self.OnDirtyUI)
0564         if navtoolbar:
0565             hs.Add(NavToolBar(self, False), 0, wx.EXPAND|wx.BOTTOM, 5)
0566         self.SetSizer(hs)
0567         hs.Fit(self)
0568 
0569     def __del__(self):
0570         pubsub.unsubscribe(self.OnUpdateCategories)
0571         super(CategoryEditor, self).__del__()
0572 
0573     def OnManageCategories(self, _):
0574         with guihelper.WXDialogWrapper(CategoryManager(self), True):
0575             pass
0576 
0577     def OnUpdateCategories(self, msg):
0578         cats=msg.data[:]
0579         cats=[self.unnamed]+cats
0580         if self.categories!=cats:
0581             self.categories=cats
0582             sel=self.category.GetStringSelection()
0583             self.category.Clear()
0584             for i in cats:
0585                 self.category.Append(i)
0586             try:
0587                 self.category.SetStringSelection(sel)
0588             except:
0589                 # the above fails if the category we are is deleted
0590                 self.category.SetStringSelection(self.unnamed)
0591 
0592     def Get(self):
0593         self.Clean()
0594         v=self.category.GetStringSelection()
0595         if len(v) and v!=self.unnamed:
0596             return {'category': v}
0597         return {}
0598 
0599     def Set(self, data):
0600         self.Ignore()
0601         if data is None:
0602             v=self.unnamed
0603         else:
0604             v=data.get("category", self.unnamed)
0605         try:
0606             self.category.SetStringSelection(v)
0607         except:
0608             assert v!=self.unnamed
0609             self.category.SetStringSelection(self.unnamed)
0610         self.Clean()
0611                 
0612 # MemoEditor--------------------------------------------------------------------
0613 class MemoEditor(DirtyUIBase):
0614 
0615     def __init__(self, parent, _, navtoolbar=False):
0616         DirtyUIBase.__init__(self, parent)
0617 
0618         _box=field_color.build_color_field(self, wx.StaticBox,
0619                                            (self, -1, "Memo"),
0620                                            'memo')
0621         vs=wx.StaticBoxSizer(_box, wx.HORIZONTAL)
0622         self.static_box=_box
0623 
0624         self.memo=wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
0625         vs.Add(self.memo, 1, wx.EXPAND|wx.ALL, 5)
0626         wx.EVT_TEXT(self, self.memo.GetId(), self.OnDirtyUI)
0627         if navtoolbar:
0628             vs.Add(NavToolBar(self, horizontal=False), 0, wx.EXPAND|wx.BOTTOM, 5)
0629         self.SetSizer(vs)
0630         vs.Fit(self)
0631 
0632     def Set(self, data):
0633         self.Ignore()
0634         if data is None:
0635             s=''
0636         else:
0637             s=data.get('memo', '')
0638         self.memo.SetValue(s)
0639         self.Clean()
0640 
0641     def Get(self):
0642         self.Clean()
0643         if len(self.memo.GetValue()):
0644             return {'memo': self.memo.GetValue()}
0645         return {}
0646 
0647     # copy/cut/paste routines
0648     def CanCopy(self):
0649         return self.memo.CanCopy()
0650     def Copy(self):
0651         return self.memo.Copy()
0652     def CanPaste(self):
0653         return self.memo.CanPaste()
0654     def Paste(self):
0655         return self.memo.Paste()
0656     def CanCut(self):
0657         return self.memo.CanCut()
0658     def Cut(self):
0659         return self.memo.Cut()
0660 
0661 # NumberEditor------------------------------------------------------------------
0662 class NumberEditor(DirtyUIBase):
0663 
0664     choices=[ ("None", "none"), ("Home", "home"), ("Office",
0665     "office"), ("Cell", "cell"), ("Fax", "fax"), ("Pager", "pager"),
0666     ("Data", "data"), ("Main", "main")]
0667 
0668     _None_Value='None'
0669 
0670     def __init__(self, parent, _, navtoolbar=False):
0671 
0672         DirtyUIBase.__init__(self, parent)
0673 
0674         _field_color_dict=field_color.build_field_info(self, 'number')
0675 
0676         hs=wx.StaticBoxSizer(field_color.build_color_field(self,
0677                                                            wx.StaticBox,
0678                                                            (self, -1, "Number details"),
0679                                                            'details',
0680                                                            _field_color_dict),
0681                              wx.VERTICAL)
0682         _hs_top=wx.BoxSizer(wx.HORIZONTAL)
0683         _txt=field_color.build_color_field(self, wx.StaticText,
0684                                            (self, -1, "Type"),
0685                                            'type', _field_color_dict)
0686         _hs_top.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0687 
0688         self.type=wx.ComboBox(self, -1, "Cell", choices=[desc for desc,name in self.choices], style=wx.CB_READONLY)
0689         _hs_top.Add(self.type, 0, wx.EXPAND|wx.ALL, 5)
0690 
0691         _txt=field_color.build_color_field(self, wx.StaticText,
0692                                            (self, -1, "SpeedDial"),
0693                                            'speeddial', _field_color_dict)
0694         _hs_top.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0695         self.speeddial=wx.TextCtrl(self, -1, "", size=(32,10))
0696         _hs_top.Add(self.speeddial, 0, wx.EXPAND|wx.ALL, 5)
0697 
0698         _txt=field_color.build_color_field(self, wx.StaticText,
0699                                            (self, -1, "Number"),
0700                                            'number', _field_color_dict)
0701         _hs_top.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0702         self.number=wx.TextCtrl(self, -1, "")
0703         _hs_top.Add(self.number, 1, wx.EXPAND|wx.ALL, 5)
0704 
0705         # add a toolbar w/ the Up/Down/Del buttons
0706         if navtoolbar:
0707             _hs_top.Add(NavToolBar(self), 0, wx.EXPAND|wx.BOTTOM, 5)
0708         hs.Add(_hs_top, 0, wx.EXPAND|wx.ALL, 0)
0709         # the bottom section
0710         _hs_bot=wx.BoxSizer(wx.HORIZONTAL)
0711         _txt=field_color.build_color_field(self, wx.StaticText,
0712                                            (self, -1, "Ringtone"),
0713                                            'ringtone', _field_color_dict)
0714         _hs_bot.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0715         self.ringtone=wx.ComboBox(self, -1)
0716         _hs_bot.Add(self.ringtone, 0, wx.EXPAND|wx.ALL, 5)
0717         _txt=field_color.build_color_field(self, wx.StaticText,
0718                                            (self, -1, "Wallpaper"),
0719                                            'wallpaper', _field_color_dict)
0720         _hs_bot.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0721         
0722         self.wallpaper=wx.ComboBox(self, -1)
0723         _hs_bot.Add(self.wallpaper, 0, wx.EXPAND|wx.ALL, 5)
0724         
0725         hs.Add(_hs_bot, 0, wx.EXPAND|wx.ALL, 0)
0726 
0727         global _wallpaper_list, _ringtone_list
0728         pubsub.subscribe(self.OnWallpaperUpdates, pubsub.ALL_WALLPAPERS)
0729         if _wallpaper_list is None:
0730             pubsub.publish(pubsub.REQUEST_WALLPAPERS)
0731         else:
0732             self._populate_wallpaper()
0733         pubsub.subscribe(self.OnRingtoneUpdates, pubsub.ALL_RINGTONES)
0734         if _ringtone_list is None:
0735             pubsub.publish(pubsub.REQUEST_RINGTONES)
0736         else:
0737             self._populate_ringtone()
0738 
0739         wx.EVT_TEXT(self, self.type.GetId(), self.OnDirtyUI)
0740         wx.EVT_TEXT(self, self.speeddial.GetId(), self.OnDirtyUI)
0741         wx.EVT_TEXT(self, self.number.GetId(), self.OnDirtyUI)
0742         wx.EVT_TEXT(self, self.ringtone.GetId(), self.OnDirtyUI)
0743         wx.EVT_TEXT(self, self.wallpaper.GetId(), self.OnDirtyUI)
0744         self.SetSizer(hs)
0745         hs.Fit(self)
0746 
0747     def __del__(self):
0748         pubsub.unsubscribe(self.OnWallpaperUpdates)
0749         pubsub.unsubscribe(self.OnRingtoneUpdates)
0750         super(NumberEditor, self).__del__()
0751 
0752     def _populate_cb(self, cb_widget, data):
0753         cb_widget.Clear()
0754         cb_widget.Append(self._None_Value)
0755         for _entry in data:
0756             cb_widget.Append(_entry)
0757 
0758     def _set_cb_sel(self, cb_widget, str_sel):
0759         if str_sel:
0760             _sel=str_sel
0761         else:
0762             _sel=self._None_Value
0763         try:
0764             cb_widget.SetStringSelection(_sel)
0765         except:
0766             cb_widget.Append(sel)
0767         cb_widget.SetStringSelection(_sel)
0768 
0769     def _get_cb_sel(self, cb_widget):
0770         _sel=cb_widget.GetStringSelection()
0771         if not _sel or _sel==self._None_Value:
0772             return None
0773         return _sel
0774 
0775     def _populate_ringtone(self):
0776         """Populate the combo box with ringtone data"""
0777         self.Ignore()
0778         _str_sel=self.ringtone.GetStringSelection()
0779         global _ringtone_list
0780         self._populate_cb(self.ringtone, _ringtone_list)
0781         self._set_cb_sel(self.ringtone, _str_sel)
0782         self.Clean()
0783 
0784     def _populate_wallpaper(self):
0785         """Ppulate the combo box with wallpaper data"""
0786         self.Ignore()
0787         _str_sel=self.wallpaper.GetStringSelection()
0788         global _wallpaper_list
0789         self._populate_cb(self.wallpaper, _wallpaper_list)
0790         self._set_cb_sel(self.wallpaper, _str_sel)
0791         self.Clean()
0792 
0793     def OnWallpaperUpdates(self, msg):
0794         global _wallpaper_list
0795         _wallpaper_list=msg.data[:]
0796         self._populate_wallpaper()
0797     def OnRingtoneUpdates(self, msg):
0798         global _ringtone_list
0799         _ringtone_list=msg.data[:]
0800         self._populate_ringtone()
0801 
0802     def Set(self, data):
0803         self.Ignore()
0804         sd=data.get("speeddial", "")
0805         if isinstance(sd,int):
0806             sd=`sd`
0807         self.speeddial.SetValue(sd)
0808         self.number.SetValue(data.get("number", ""))
0809         # ringtone & wallpaper
0810         self._set_cb_sel(self.ringtone, data.get('ringtone', None))
0811         self._set_cb_sel(self.wallpaper, data.get('wallpaper', None))
0812         # number of type
0813         v=data.get("type", "cell")
0814         for i in range(len(self.choices)):
0815             if self.choices[i][1]==v:
0816                 self.type.SetSelection(i)
0817                 self.Clean()
0818                 return
0819         self.type.SetSelection(0)
0820         self.Clean()
0821 
0822     def Get(self):
0823         self.Clean()
0824         res={}
0825         if len(self.number.GetValue())==0:
0826             return res
0827         res['number']=self.number.GetValue()
0828         if len(self.speeddial.GetValue()):
0829             res['speeddial']=self.speeddial.GetValue()
0830             try:
0831                 res['speeddial']=int(res['speeddial'])
0832             except:
0833                 pass
0834         res['type']=self.choices[self.type.GetSelection()][1]
0835         _sel=self._get_cb_sel(self.ringtone)
0836         if _sel:
0837             res['ringtone']=_sel
0838         _sel=self._get_cb_sel(self.wallpaper)
0839         if _sel:
0840             res['wallpaper']=_sel
0841         return res
0842 
0843 # EmailEditor-------------------------------------------------------------------
0844 class EmailEditor(DirtyUIBase):
0845 
0846     ID_TYPE=wx.NewId()
0847     _None_Value='None'
0848 
0849     def __init__(self, parent, _, navtoolbar=False):
0850         super(EmailEditor, self).__init__(parent)
0851 
0852         _field_color_dict=field_color.build_field_info(self, 'email_details')
0853 
0854         _box=field_color.build_color_field(self, wx.StaticBox,
0855                                            (self, -1, 'Email Address'),
0856                                            'email')
0857         hs=wx.StaticBoxSizer(_box, wx.VERTICAL)
0858         # top section
0859         _hs_top=wx.BoxSizer(wx.HORIZONTAL)
0860         self.type=wx.ComboBox(self, self.ID_TYPE, "", choices=["", "Home", "Business"], style=wx.CB_READONLY)
0861         _hs_top.Add(self.type, 0, wx.EXPAND|wx.ALL, 5)
0862         self.email=wx.TextCtrl(self, -1, "")
0863         _hs_top.Add(self.email, 1, wx.EXPAND|wx.ALL, 5)
0864         if navtoolbar:
0865             _hs_top.Add(NavToolBar(self), 0, wx.EXPAND|wx.BOTTOM, 5)
0866         hs.Add(_hs_top, 0, wx.EXPAND|wx.ALL, 0)
0867         # bottom section
0868         _hs_bot=wx.BoxSizer(wx.HORIZONTAL)
0869         _txt=field_color.build_color_field(self, wx.StaticText,
0870                                            (self, -1, "SpeedDial"),
0871                                            'emailspeeddial', _field_color_dict)
0872         _hs_bot.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0873         self.speeddial=wx.TextCtrl(self, -1, "", size=(32,10))
0874         _hs_bot.Add(self.speeddial, 0, wx.EXPAND|wx.ALL, 5)
0875         _txt=field_color.build_color_field(self, wx.StaticText,
0876                                            (self, -1, "Ringtone"),
0877                                            'emailringtone', _field_color_dict)
0878         _hs_bot.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0879         self.ringtone=wx.ComboBox(self, -1)
0880         _hs_bot.Add(self.ringtone, 0, wx.EXPAND|wx.ALL, 5)
0881         _txt=field_color.build_color_field(self, wx.StaticText,
0882                                            (self, -1, "Wallpaper"),
0883                                            'emailwallpaper', _field_color_dict)
0884         _hs_bot.Add(_txt, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0885         
0886         self.wallpaper=wx.ComboBox(self, -1)
0887         _hs_bot.Add(self.wallpaper, 0, wx.EXPAND|wx.ALL, 5)
0888         
0889         hs.Add(_hs_bot, 0, wx.EXPAND|wx.ALL, 0)
0890 
0891         global _wallpaper_list, _ringtone_list
0892         pubsub.subscribe(self.OnWallpaperUpdates, pubsub.ALL_WALLPAPERS)
0893         if _wallpaper_list is None:
0894             pubsub.publish(pubsub.REQUEST_WALLPAPERS)
0895         else:
0896             self._populate_wallpaper()
0897         pubsub.subscribe(self.OnRingtoneUpdates, pubsub.ALL_RINGTONES)
0898         if _ringtone_list is None:
0899             pubsub.publish(pubsub.REQUEST_RINGTONES)
0900         else:
0901             self._populate_ringtone()
0902 
0903         wx.EVT_TEXT(self, self.type.GetId(), self.OnDirtyUI)
0904         wx.EVT_TEXT(self, self.email.GetId(), self.OnDirtyUI)
0905         wx.EVT_TEXT(self, self.speeddial.GetId(), self.OnDirtyUI)
0906         wx.EVT_TEXT(self, self.ringtone.GetId(), self.OnDirtyUI)
0907         wx.EVT_TEXT(self, self.wallpaper.GetId(), self.OnDirtyUI)
0908         self.SetSizer(hs)
0909         hs.Fit(self)
0910 
0911     def __del__(self):
0912         pubsub.unsubscribe(self.OnWallpaperUpdates)
0913         pubsub.unsubscribe(self.OnRingtoneUpdates)
0914         super(NumberEditor, self).__del__()
0915 
0916     def _populate_cb(self, cb_widget, data):
0917         cb_widget.Clear()
0918         cb_widget.Append(self._None_Value)
0919         for _entry in data:
0920             cb_widget.Append(_entry)
0921 
0922     def _set_cb_sel(self, cb_widget, str_sel):
0923         if str_sel:
0924             _sel=str_sel
0925         else:
0926             _sel=self._None_Value
0927         try:
0928             cb_widget.SetStringSelection(_sel)
0929         except:
0930             cb_widget.Append(sel)
0931         cb_widget.SetStringSelection(_sel)
0932 
0933     def _get_cb_sel(self, cb_widget):
0934         _sel=cb_widget.GetStringSelection()
0935         if not _sel or _sel==self._None_Value:
0936             return None
0937         return _sel
0938 
0939     def _populate_ringtone(self):
0940         """Populate the combo box with ringtone data"""
0941         self.Ignore()
0942         _str_sel=self.ringtone.GetStringSelection()
0943         global _ringtone_list
0944         self._populate_cb(self.ringtone, _ringtone_list)
0945         self._set_cb_sel(self.ringtone, _str_sel)
0946         self.Clean()
0947 
0948     def _populate_wallpaper(self):
0949         """Ppulate the combo box with wallpaper data"""
0950         self.Ignore()
0951         _str_sel=self.wallpaper.GetStringSelection()
0952         global _wallpaper_list
0953         self._populate_cb(self.wallpaper, _wallpaper_list)
0954         self._set_cb_sel(self.wallpaper, _str_sel)
0955         self.Clean()
0956 
0957     def OnWallpaperUpdates(self, msg):
0958         global _wallpaper_list
0959         _wallpaper_list=msg.data[:]
0960         self._populate_wallpaper()
0961     def OnRingtoneUpdates(self, msg):
0962         global _ringtone_list
0963         _ringtone_list=msg.data[:]
0964         self._populate_ringtone()
0965 
0966     def Set(self, data):
0967         self.Ignore()
0968         self.email.SetValue(data.get("email", ""))
0969         sd=data.get("speeddial", "")
0970         if isinstance(sd, int):
0971             sd=`sd`
0972         self.speeddial.SetValue(sd)
0973         self._set_cb_sel(self.ringtone, data.get('ringtone', None))
0974         self._set_cb_sel(self.wallpaper, data.get('wallpaper', None))
0975         v=data.get("type", "")
0976         if v=="home":
0977             self.type.SetSelection(1)
0978         elif v=="business":
0979             self.type.SetSelection(2)
0980         else:
0981             self.type.SetSelection(0)
0982         self.Clean()
0983 
0984     def Get(self):
0985         self.Clean()
0986         res={}
0987         if len(self.email.GetValue())==0:
0988             return res
0989         res['email']=self.email.GetValue()
0990         if len(self.speeddial.GetValue()):
0991             res['speeddial']=self.speeddial.GetValue()
0992             try:
0993                 res['speeddial']=int(res['speeddial'])
0994             except:
0995                 pass
0996         if self.type.GetSelection()==1:
0997             res['type']='home'
0998         elif self.type.GetSelection()==2:
0999             res['type']='business'
1000         _sel=self._get_cb_sel(self.ringtone)
1001         if _sel:
1002             res['ringtone']=_sel
1003         _sel=self._get_cb_sel(self.wallpaper)
1004         if _sel:
1005             res['wallpaper']=_sel
1006         return res
1007 
1008 # URLEditor---------------------------------------------------------------------
1009 class URLEditor(DirtyUIBase):
1010 
1011     ID_TYPE=wx.NewId()
1012     def __init__(self, parent, _, navtoolbar=False):
1013         super(URLEditor, self).__init__(parent)
1014 
1015         _box=field_color.build_color_field(self, wx.StaticBox,
1016                                            (self, -1, "URL"), 'url')
1017         hs=wx.StaticBoxSizer(_box, wx.HORIZONTAL)
1018 
1019         self.type=wx.ComboBox(self, self.ID_TYPE, "", choices=["", "Home", "Business"], style=wx.CB_READONLY)
1020         hs.Add(self.type, 0, wx.EXPAND|wx.ALL, 5)
1021         self.url=wx.TextCtrl(self, -1, "")
1022         hs.Add(self.url, 1, wx.EXPAND|wx.ALL, 5)
1023         if navtoolbar:
1024             hs.Add(NavToolBar(self), 0, wx.EXPAND|wx.BOTTOM, 5)
1025         wx.EVT_TEXT(self, self.type.GetId(), self.OnDirtyUI)
1026         wx.EVT_TEXT(self, self.url.GetId(), self.OnDirtyUI)
1027         self.SetSizer(hs)
1028         hs.Fit(self)
1029 
1030     def Set(self, data):
1031         self.Ignore()
1032         self.url.SetValue(data.get("url", ""))
1033         v=data.get("type", "")
1034         if v=="home":
1035             self.type.SetSelection(1)
1036         elif v=="business":
1037             self.type.SetSelection(2)
1038         else:
1039             self.type.SetSelection(0)
1040         self.Clean()
1041 
1042     def Get(self):
1043         self.Clean()
1044         res={}
1045         if len(self.url.GetValue())==0:
1046             return res
1047         res['url']=self.url.GetValue()
1048         if self.type.GetSelection()==1:
1049             res['type']='home'
1050         elif self.type.GetSelection()==2:
1051             res['type']='business'
1052         return res
1053 
1054 # AddressEditor-----------------------------------------------------------------
1055 class AddressEditor(DirtyUIBase):
1056 
1057     ID_TYPE=wx.NewId()
1058 
1059     fieldinfos=("street", "Street"), ("street2", "Street2"), ("city", "City"), \
1060             ("state", "State"), ("postalcode", "Postal/Zipcode"), ("country", "Country/Region")
1061 
1062     def __init__(self, parent, _, navtoolbar=False):
1063         super(AddressEditor, self).__init__(parent)
1064 
1065         _fc_dict=field_color.build_field_info(self, 'address')
1066         _hs=wx.StaticBoxSizer(field_color.build_color_field(self, wx.StaticBox,
1067                                           (self, -1, "Address Details"),
1068                                            'details', _fc_dict),
1069                               wx.HORIZONTAL)
1070         vs=wx.BoxSizer(wx.VERTICAL)
1071         hs=wx.BoxSizer(wx.HORIZONTAL)
1072         hs.Add(field_color.build_color_field(self, wx.StaticText,
1073                                              (self, -1, "Type"),
1074                                              'type', _fc_dict),
1075                0, wx.ALIGN_CENTRE|wx.ALL, 5)
1076         self.type=wx.ComboBox(self, self.ID_TYPE, "Home", choices=["Home", "Business"], style=wx.CB_READONLY)
1077         hs.Add(self.type, 0, wx.EXPAND|wx.ALL, 5)
1078         hs.Add(field_color.build_color_field(self, wx.StaticText,
1079                                              (self, -1, "Company"),
1080                                              'company', _fc_dict),
1081                0, wx.ALIGN_CENTRE|wx.ALL, 5)
1082         self.company=wx.TextCtrl(self, -1, "")
1083         hs.Add(self.company, 1, wx.EXPAND|wx.ALL, 5)
1084 
1085         gs=wx.FlexGridSizer(6,2,2,5)
1086 
1087         for name,desc in self.fieldinfos:
1088             gs.Add(field_color.build_color_field(self, wx.StaticText,
1089                                                  (self, -1, desc),
1090                                                  name, _fc_dict),
1091                    0, wx.ALIGN_CENTRE)
1092             setattr(self, name, wx.TextCtrl(self, -1, ""))
1093             gs.Add(getattr(self,name), 1, wx.EXPAND)
1094 
1095         gs.AddGrowableCol(1)
1096 
1097         vs.Add(hs,0,wx.EXPAND|wx.ALL, 5)
1098         vs.Add(gs,0,wx.EXPAND|wx.ALL, 5)
1099 
1100         _hs.Add(vs, 0, wx.EXPAND|wx.ALL, 5)
1101         if navtoolbar:
1102             _hs.Add(NavToolBar(self, horizontal=False), 0, wx.EXPAND|wx.BOTTOM, 5)
1103         # ::TODO:: disable company when type is home
1104         wx.EVT_TEXT(self, self.type.GetId(), self.OnDirtyUI)
1105         wx.EVT_TEXT(self, self.company.GetId(), self.OnDirtyUI)
1106         for name,_ in self.fieldinfos:
1107             wx.EVT_TEXT(self, getattr(self, name).GetId(), self.OnDirtyUI)
1108         self.SetSizer(_hs)
1109         vs.Fit(self)
1110 
1111     def Set(self, data):
1112         self.Ignore()
1113         # most fields
1114         for name,ignore in self.fieldinfos:
1115             getattr(self, name).SetValue(data.get(name, ""))
1116         # special cases
1117         self.company.SetValue(data.get("company", ""))
1118         if data.get("type", "home")=="home":
1119             self.type.SetValue("Home")
1120         else:
1121             self.type.SetValue("Business")
1122         self.Clean()
1123 
1124     def Get(self):
1125         self.Clean()
1126         res={}
1127         # most fields
1128         for name,ignore in self.fieldinfos:
1129             w=getattr(self, name)
1130             if len(w.GetValue()):
1131                 res[name]=w.GetValue()
1132         # special cases
1133         if self.type.GetSelection()==1:
1134             if len(self.company.GetValue()):
1135                 res['company']=self.company.GetValue()
1136         # only add in type field if any other type field is set
1137         if len(res):
1138             res['type']=['home', 'business'][self.type.GetSelection()]
1139         return res
1140 
1141 # NameEditor--------------------------------------------------------------------
1142 class NameEditor(DirtyUIBase):
1143 
1144     def __init__(self, parent, _, navtoolbar=False):
1145         super(NameEditor, self).__init__(parent)
1146 
1147         _fc_dict=field_color.build_field_info(self, 'name')
1148         _hs=wx.StaticBoxSizer(field_color.build_color_field(self,
1149                                                             wx.StaticBox,
1150                                                             (self, -1, 'Name Details'),
1151                                                             'details', _fc_dict),
1152                               wx.HORIZONTAL)
1153         vs=wx.BoxSizer(wx.VERTICAL)
1154         hstop=wx.BoxSizer(wx.HORIZONTAL)
1155         hsbot=wx.BoxSizer(wx.HORIZONTAL)
1156         hstop.Add(field_color.build_color_field(self, wx.StaticText,
1157                                                 (self, -1, "First"),
1158                                                 'first', _fc_dict),
1159                   0, wx.ALIGN_CENTRE|wx.ALL,5)
1160         self.first=wx.TextCtrl(self, -1, "")
1161         hstop.Add(self.first, 1, wx.EXPAND|wx.ALL, 5)
1162         hstop.Add(field_color.build_color_field(self, wx.StaticText,
1163                                                 (self, -1, "Middle"),
1164                                                 'middle', _fc_dict),
1165                   0, wx.ALIGN_CENTRE|wx.ALL,5)
1166         self.middle=wx.TextCtrl(self, -1, "")
1167         hstop.Add(self.middle, 1, wx.EXPAND|wx.ALL, 5)
1168         hstop.Add(field_color.build_color_field(self, wx.StaticText,
1169                                                 (self, -1, "Last"),
1170                                                 'last', _fc_dict),
1171                   0, wx.ALIGN_CENTRE|wx.ALL,5)
1172         self.last=wx.TextCtrl(self, -1, "")
1173         hstop.Add(self.last, 1, wx.EXPAND|wx.ALL, 5)
1174         hsbot.Add(field_color.build_color_field(self, wx.StaticText,
1175                                                 (self, -1, "Full"),
1176                                                 'full', _fc_dict),
1177                   0, wx.ALIGN_CENTRE|wx.ALL,5)
1178         self.full=wx.TextCtrl(self, -1, "")
1179         hsbot.Add(self.full, 4, wx.EXPAND|wx.ALL, 5)
1180         hsbot.Add(field_color.build_color_field(self, wx.StaticText,
1181                                                 (self, -1, "Nickname"),
1182                                                 'nickname', _fc_dict),
1183                   0, wx.ALIGN_CENTRE|wx.ALL,5)
1184         self.nickname=wx.TextCtrl(self, -1, "")
1185         hsbot.Add(self.nickname, 1, wx.EXPAND|wx.ALL, 5)
1186         vs.Add(hstop, 0, wx.EXPAND|wx.ALL, 5)
1187         vs.Add(hsbot, 0, wx.EXPAND|wx.ALL, 5)
1188         _hs.Add(vs, 0, wx.EXPAND|wx.ALL, 5)
1189         # add a toolbar w/ the Up/Down/Del buttons
1190         if navtoolbar:
1191             _hs.Add(NavToolBar(self, horizontal=False), 0, wx.EXPAND, 0)
1192         for _name in ('first', 'middle', 'last', 'full', 'nickname'):
1193             wx.EVT_TEXT(self, getattr(self, _name).GetId(), self.OnDirtyUI)
1194 
1195         # use the sizer and resize ourselves according to space needed by sizer
1196         self.SetSizer(_hs)
1197         vs.Fit(self)
1198 
1199     def Set(self, data):
1200         self.Ignore()
1201         self.first.SetValue(data.get("first", ""))
1202         self.middle.SetValue(data.get("middle", ""))
1203         self.last.SetValue(data.get("last", ""))
1204         self.full.SetValue(data.get("full", ""))
1205         self.nickname.SetValue(data.get("nickname", ""))
1206         self.Clean()
1207 
1208     def Get(self):
1209         self.Clean()
1210         res={}
1211         for name,widget in ( "first", self.first), ("middle", self.middle), ("last", self.last), \
1212             ("full", self.full), ("nickname", self.nickname):
1213             if len(widget.GetValue()):
1214                 res[name]=widget.GetValue()
1215         return res
1216 
1217 # MiscEditor-----------------------------------------------------------------
1218 class MiscEditor(DirtyUIBase):
1219     def __init__(self, parent, _, navtoolbar=False):
1220         super(MiscEditor, self).__init__(parent)
1221         _fc_dict=field_color.build_field_info(self, 'phonebook')
1222         vs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Misc Details"),
1223                              wx.VERTICAL)
1224         # storage field
1225         hs=wx.BoxSizer(wx.HORIZONTAL)
1226         hs.Add(field_color.build_color_field(self, wx.StaticText,
1227                                              (self, -1, "Storage Option:"),
1228                                              'storage', _fc_dict),
1229                0, wx.ALIGN_CENTRE|wx.ALL, 5)
1230         self._storage=wx.ComboBox(self, -1, 'Phone', choices=["Phone", "SIM"],
1231                                   style=wx.CB_READONLY)
1232         wx.EVT_COMBOBOX(self, self._storage.GetId(), self.OnDirtyUI)
1233         hs.Add(self._storage, 0, wx.EXPAND|wx.LEFT, 5)
1234         vs.Add(hs, 0, wx.EXPAND|wx.ALL, 5)
1235         # secret field
1236         self._secret=field_color.build_color_field(self, wx.CheckBox,
1237                                                    (self, -1,
1238                                                     'This entry is private/secret'),
1239                                                    'secret', _fc_dict)
1240 
1241         wx.EVT_CHECKBOX(self, self._secret.GetId(), self.OnDirtyUI)
1242         vs.Add(self._secret, 0, wx.EXPAND|wx.ALL, 5)
1243         # all done
1244         self.SetSizer(vs)
1245         vs.Fit(self)
1246 
1247     def Set(self, data):
1248         self._storage.SetValue('SIM' if data.get('sim', False) else 'Phone')
1249         self._secret.SetValue(data.get('secret', False))
1250 
1251     def Get(self):
1252         _res={}
1253         if self._storage.GetValue()=='SIM':
1254             _res['sim']=True
1255         if self._secret.GetValue():
1256             _res['secret']=True
1257         return _res
1258 
1259 #  ICEEditor-----------------------------------------------------------------
1260 class ICEEditor(DirtyUIBase):
1261     def __init__(self, parent, _, navtoolbar=False):
1262         super(ICEEditor, self).__init__(parent)
1263         _fc_dict=field_color.build_field_info(self, 'phonebook')
1264         vs=wx.StaticBoxSizer(field_color.build_color_field(self,
1265                                                            wx.StaticBox,
1266                                                            (self, -1, "ICE Details"),
1267                                                            'ICE', _fc_dict),
1268                              wx.VERTICAL)
1269         # ICE field
1270         hs=wx.BoxSizer(wx.HORIZONTAL)
1271         hs.Add(field_color.build_color_field(self, wx.StaticText,
1272                                              (self, -1, "Assign this contact as:"),
1273                                              'ICE', _fc_dict),
1274                0, wx.ALIGN_CENTRE|wx.ALL, 5)
1275         self._ice=wx.ComboBox(self, -1, 'None',
1276                                   choices=['None', 'ICE 1', 'ICE 2', 'ICE 3'],
1277                                   style=wx.CB_READONLY)
1278         wx.EVT_COMBOBOX(self, self._ice.GetId(), self.OnDirtyUI)
1279         hs.Add(self._ice, 0, wx.EXPAND|wx.LEFT, 5)
1280         vs.Add(hs, 0, wx.EXPAND|wx.ALL, 5)
1281         # all done
1282         self.SetSizer(vs)
1283         vs.Fit(self)
1284 
1285     def Set(self, data):
1286         if data.has_key('iceindex'):
1287             _val=data['iceindex']+1
1288         else:
1289             _val=0
1290         self._ice.SetSelection(_val)
1291 
1292     def Get(self):
1293         _res={}
1294         _val=self._ice.GetSelection()
1295         if _val:
1296             _res['iceindex']=_val-1
1297         return _res
1298 
1299 # EditorManager-----------------------------------------------------------------
1300 class EditorManager(fixedscrolledpanel.wxScrolledPanel):
1301 
1302     ID_DOWN=wx.NewId()
1303     ID_UP=wx.NewId()
1304     ID_ADD=wx.NewId()
1305     ID_DELETE=wx.NewId()
1306     instruction_text="""
1307 \n\nPress Add above to add a field.  Press Delete to remove the field your
1308 cursor is on.
1309 
1310 You can use Up and Down to change the priority of items.  For example, some
1311 phones store the first five numbers in the numbers tab, and treat the first
1312 number as the default to call.  Other phones can only store one email address
1313 so only the first one would be stored.
1314 """
1315 
1316     def __init__(self, parent, childclass):
1317         """Constructor
1318 
1319         @param parent: Parent window
1320         @param childclass: One of the *Editor classes which is used as a factory for making the
1321                widgets that correspond to each value"""
1322         fixedscrolledpanel.wxScrolledPanel.__init__(self, parent)
1323         self.dirty_ui_handler=getattr(parent, 'OnDirtyUI', None)
1324         self.sizer=wx.BoxSizer(wx.VERTICAL)
1325         self.SetSizer(self.sizer)
1326         self.widgets=[]
1327         self.childclass=childclass
1328         self.instructions=wx.StaticText(self, -1, EditorManager.instruction_text)
1329         self.sizer.Add(self.instructions, 0, wx.ALIGN_CENTER )
1330         self.SetupScrolling()
1331 
1332     def Get(self):
1333         """Returns a list of dicts corresponding to the values"""
1334         res=[]
1335         for i in self.widgets:
1336             g=i.Get()
1337             if len(g):
1338                 res.append(g)
1339         return res
1340 
1341     def Populate(self, data):
1342         """Fills in the editors according to the list of dicts in data
1343 
1344         The editor widgets are created and destroyed as needed"""
1345         callsus=False
1346         while len(data)>len(self.widgets):
1347             callsus=True
1348             _w=self.childclass(self, len(self.widgets), navtoolbar=True)
1349             if self.dirty_ui_handler:
1350                 EVT_DIRTY_UI(self, _w.GetId(), self.dirty_ui_handler)
1351             self.widgets.append(_w)
1352             self.sizer.Add(_w, 0, wx.EXPAND|wx.ALL, 10)
1353         while len(self.widgets)>len(data):
1354             callsus=True
1355             self.sizer.Remove(self.widgets[-1])
1356             self.widgets[-1].Destroy()
1357             del self.widgets[-1]
1358         for num in range(len(data)):
1359             self.widgets[num].Clean()
1360             self.widgets[num].Set(data[num])
1361         callsus=self.DoInstructionsLayout() or callsus
1362         if callsus:
1363             self.sizer.Layout()
1364             self.SetupScrolling()
1365 
1366     def DoInstructionsLayout(self):
1367         "Returns True if Layout should be called"
1368         if len(self.widgets):
1369             if self.instructions.IsShown():
1370                 self.sizer.Remove(self.instructions)
1371                 self.instructions.Show(False)
1372                 return True
1373         else:
1374             if not self.instructions.IsShown():
1375                 self.sizer.Add(self.instructions, 0, wx.ALIGN_CENTER )
1376                 self.instructions.Show(True)
1377                 return True
1378         return False
1379 
1380 
1381     def GetCurrentWidgetIndex(self):
1382         """Returns the index of the currently selected editor widget
1383 
1384         @raise IndexError: if there is no selected one"""
1385         focuswin=wx.Window.FindFocus()
1386         win=focuswin
1387         while win is not None and win not in self.widgets:
1388             win=win.GetParent()
1389         if win is None:
1390             raise IndexError("no idea who is selected")
1391         if win not in self.widgets:
1392             raise IndexError("no idea what that thing is")
1393         pos=self.widgets.index(win)
1394         return pos
1395 
1396     def Add(self):
1397         """Adds a new widget at the currently selected location"""
1398         gets=[x.Get() for x in self.widgets]
1399         try:
1400             pos=self.GetCurrentWidgetIndex()
1401         except IndexError:
1402             pos=len(gets)-1
1403         _w=self.childclass(self, len(self.widgets), navtoolbar=True)
1404         if self.dirty_ui_handler:
1405             EVT_DIRTY_UI(self, _w.GetId(), self.dirty_ui_handler)
1406             self.dirty_ui_handler(None)
1407         self.widgets.append(_w)
1408         self.sizer.Add(_w, 0, wx.EXPAND|wx.ALL, 10)
1409         self.DoInstructionsLayout() 
1410         self.sizer.Layout()
1411         self.SetupScrolling()
1412         if len(self.widgets)>1:
1413             for num,value in zip( range(pos+2, len(self.widgets)), gets[pos+1:]):
1414                 self.widgets[num].Set(value)
1415             self.widgets[pos+1].Set({})
1416             self.widgets[pos+1].SetFocus()
1417         else:
1418             self.widgets[0].SetFocus()
1419 
1420     def MoveField(self, field, delta):
1421         try:
1422             pos=self.widgets.index(field)
1423         except IndexError:
1424             wx.Bell()
1425             return
1426         if pos+delta<0:
1427             print "that would go off top"
1428             return
1429         if pos+delta>=len(self.widgets):
1430             print "that would go off bottom"
1431             return
1432         if self.dirty_ui_handler:
1433             self.dirty_ui_handler(None)
1434         gets=[x.Get() for x in self.widgets]
1435         # swap value
1436         path,settings=self.GetWidgetPathAndSettings(self.widgets[pos], field)
1437         self.widgets[pos+delta].Set(gets[pos])
1438         self.widgets[pos].Set(gets[pos+delta])
1439         self.SetWidgetPathAndSettings(self.widgets[pos+delta], path, settings)
1440 
1441     def DeleteField(self, field):
1442         """Deletes the currently select widget"""
1443         # ignore if there is nothing to delete
1444         if len(self.widgets)==0:
1445             return
1446         # get the current value of all widgets
1447         gets=[x.Get() for x in self.widgets]
1448         try:
1449             pos=self.widgets.index(field)
1450         except IndexError:
1451             wx.Bell()
1452             return
1453         if self.dirty_ui_handler:
1454             self.dirty_ui_handler(None)
1455         # remove the last widget (the UI, not the value)
1456         self.sizer.Remove(self.widgets[-1])
1457         self.widgets[-1].Destroy()
1458         del self.widgets[-1]
1459         # if we deleted last item and it had focus, move focus
1460         # to second to last item
1461         if len(self.widgets):
1462             if pos==len(self.widgets):
1463                 self.widgets[pos-1].SetFocus()
1464         self.DoInstructionsLayout() 
1465         self.sizer.Layout()
1466         self.SetupScrolling()
1467 
1468         # update from one we deleted to end
1469         for i in range(pos, len(self.widgets)):
1470             self.widgets[i].Set(gets[i+1])
1471             
1472         if len(self.widgets):
1473             # change focus if we deleted the last widget
1474             if pos<len(self.widgets):
1475                 self.widgets[pos].SetFocus()
1476 
1477     def Delete(self):
1478         """Deletes the currently select widget"""
1479         # ignore if there is nothing to delete
1480         if len(self.widgets)==0:
1481             return
1482         # get the current value of all widgets
1483         gets=[x.Get() for x in self.widgets]
1484         try:
1485             pos=self.GetCurrentWidgetIndex()
1486         except IndexError:
1487             wx.Bell()
1488             return
1489         # remove the last widget (the UI, not the value)
1490         self.sizer.Remove(self.widgets[-1])
1491         self.widgets[-1].Destroy()
1492         del self.widgets[-1]
1493         # if we deleted last item and it had focus, move focus
1494         # to second to last item
1495         if len(self.widgets):
1496             if pos==len(self.widgets):
1497                 self.widgets[pos-1].SetFocus()
1498         self.DoInstructionsLayout() 
1499         self.sizer.Layout()
1500         self.SetupScrolling()
1501 
1502         # update from one we deleted to end
1503         for i in range(pos, len(self.widgets)):
1504             self.widgets[i].Set(gets[i+1])
1505             
1506         if len(self.widgets):
1507             # change focus if we deleted the last widget
1508             if pos<len(self.widgets):
1509                 self.widgets[pos].SetFocus()
1510 
1511 
1512     def Move(self, delta):
1513         """Moves the currently selected widget
1514 
1515         @param delta: positive to move down, negative to move up
1516         """
1517         focuswin=wx.Window_FindFocus()
1518         try:
1519             pos=self.GetCurrentWidgetIndex()
1520         except IndexError:
1521             wx.Bell()
1522             return
1523         if pos+delta<0:
1524             print "that would go off top"
1525             return
1526         if pos+delta>=len(self.widgets):
1527             print "that would go off bottom"
1528             return
1529         gets=[x.Get() for x in self.widgets]
1530         # swap value
1531         path,settings=self.GetWidgetPathAndSettings(self.widgets[pos], focuswin)
1532         self.widgets[pos+delta].Set(gets[pos])
1533         self.widgets[pos].Set(gets[pos+delta])
1534         self.SetWidgetPathAndSettings(self.widgets[pos+delta], path, settings)
1535 
1536     def GetWidgetPathAndSettings(self, widgetfrom, controlfrom):
1537         """Finds the specified control within the editor widgetfrom.
1538         The values are for calling L{SetWidgetPathAndSettings}.
1539         
1540         Returns a tuple of (path, settings).  path corresponds
1541         to the hierarchy with an editor (eg a panel contains a
1542         radiobox contains the radio button widget).  settings
1543         means something to L{SetWidgetPathAndSettings}.  For example,
1544         if the widget is a text widget it contains the current insertion
1545         point and selection."""
1546         # we find where the control is in the hierarchy of widgetfrom
1547         path=[]
1548 
1549         # this is the same algorithm getpwd uses on Unix
1550         win=controlfrom
1551         while win is not widgetfrom:
1552             p=win.GetParent()
1553             kiddies=p.GetChildren()
1554             found=False
1555             for kid in range(len(kiddies)):
1556                 if kiddies[kid] is win:
1557                     path=[kid]+path
1558                     win=p
1559                     found=True
1560                     break
1561             if found:
1562                 continue
1563             print "i don't appear to be my parent's child!!!"
1564             return
1565 
1566 
1567         # save some settings we know about
1568         settings=[]
1569         if isinstance(controlfrom, wx.TextCtrl):
1570             settings=[controlfrom.GetInsertionPoint(), controlfrom.GetSelection()]
1571 
1572         return path,settings
1573 
1574     def SetWidgetPathAndSettings(self,widgetto,path,settings):
1575         """See L{GetWidgetPathAndSettings}"""
1576         # now have the path.  follow it in widgetto
1577         print path
1578         win=widgetto
1579         for p in path:
1580             kids=win.GetChildren()
1581             win=kids[p]
1582         controlto=win
1583 
1584         controlto.SetFocus()
1585 
1586         if isinstance(controlto, wx.TextCtrl):
1587             controlto.SetInsertionPoint(settings[0])
1588             controlto.SetSelection(settings[1][0], settings[1][1])
1589                         
1590     def SetFocusOnValue(self, index):
1591         """Sets focus to the editor widget corresponding to the supplied index"""
1592         wx.CallAfter(self.widgets[index].SetFocus)
1593 
1594 # Editor------------------------------------------------------------------------
1595 class Editor(wx.Dialog):
1596     "The Editor Dialog itself.  It contains panes for the various field types."
1597     
1598     ID_DOWN=wx.NewId()
1599     ID_UP=wx.NewId()
1600     ID_ADD=wx.NewId()
1601     ID_DELETE=wx.NewId()
1602 
1603     color_field_name='phonebook'
1604 
1605     # the tabs and classes within them
1606     tabsfactory=[
1607         ("Names", "names", NameEditor),
1608         ("Numbers", "numbers", NumberEditor),
1609         ("Emails",  "emails", EmailEditor),
1610         ("Addresses", "addresses", AddressEditor),
1611         ("URLs", "urls", URLEditor),
1612         ("Memos", "memos", MemoEditor),
1613         ("Categories", "categories", CategoryEditor),
1614         ("Wallpapers", "wallpapers", WallpaperEditor),
1615         ("Ringtones", "ringtones", RingtoneEditor),
1616         ("ICE", 'ice', ICEEditor),
1617         ("Misc", 'flags', MiscEditor),
1618         ]
1619 
1620     def __init__(self, parent, data, title="Edit PhoneBook Entry",
1621                  keytoopenon=None, dataindex=None,
1622                  factory=database.dictdataobjectfactory, readonly=False,
1623                  datakey=None, movement=False):
1624         """Constructor for phonebookentryeditor dialog
1625 
1626         @param parent: parent window
1627         @param data: dict of values to edit
1628         @param title: window title
1629         @param keytoopenon: The key to open on. This is the key as stored in the data such as "names", "numbers"
1630         @param dataindex: Which value within the tab specified by keytoopenon to set focus to
1631         @param readonly: Indicates read-only data.
1632         """
1633         global _ringtone_list, _wallpaper_list        
1634         wx.Dialog.__init__(self, parent, -1, title, size=(740,580), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1635         field_color.get_color_info_from_profile(self)
1636         _ringtone_list=None
1637         _wallpaper_list=None
1638         self._data_key=datakey
1639         if movement and datakey is None and __debug__:
1640             self.log('Movement and datakey is None')
1641             raise ValueError
1642         # make a copy of the data we are going to work on
1643         self.dirty_widgets={ True: [], False: [] }
1644         self.data=factory.newdataobject(data)
1645         vs=wx.BoxSizer(wx.VERTICAL)
1646         # the title & direction button
1647         _hbs=wx.BoxSizer(wx.HORIZONTAL)
1648         self._title=wx.StaticText(self, -1, "Name here", style=wx.ALIGN_CENTRE|wx.ST_NO_AUTORESIZE)
1649         _add_btn=wx.BitmapButton(self, wx.NewId(),
1650                                  wx.ArtProvider.GetBitmap(guihelper.ART_ADD_FIELD), name="Prev Item")
1651         if movement:
1652             _prev_btn=wx.BitmapButton(self, wx.NewId(), wx.ArtProvider.GetBitmap(guihelper.ART_ARROW_LEFT), name="Prev Item")
1653             _next_btn=wx.BitmapButton(self, wx.NewId(), wx.ArtProvider.GetBitmap(guihelper.ART_ARROW_RIGHT), name="Next Item")
1654             self.dirty_widgets[False].append(_prev_btn)
1655             self.dirty_widgets[False].append(_next_btn)
1656             _hbs.Add(_prev_btn, 0, wx.EXPAND, 0)
1657             _hbs.Add(_add_btn, 0, wx.EXPAND|wx.LEFT, 10)
1658             _hbs.Add(self._title, 1, wx.EXPAND, 0)
1659             _hbs.Add(_next_btn, 0, wx.EXPAND, 0)
1660             wx.EVT_BUTTON(self, _prev_btn.GetId(), self.OnMovePrev)
1661             wx.EVT_BUTTON(self, _next_btn.GetId(), self.OnMoveNext)
1662         else:
1663             _hbs.Add(_add_btn, 0, wx.EXPAND|wx.LEFT, 10)
1664             _hbs.Add(self._title, 1, wx.EXPAND, 0)
1665         wx.EVT_BUTTON(self, _add_btn.GetId(), self.Add)
1666         vs.Add(_hbs, 0, wx.ALL|wx.EXPAND, 5)
1667 
1668         nb=wx.Notebook(self, -1)
1669         self.nb=nb
1670         self.nb.OnDirtyUI=self.OnDirtyUI
1671         vs.Add(nb,1,wx.EXPAND|wx.ALL,5)
1672 
1673         self.tabs=[]
1674         # instantiate the nb widgets
1675         for name,key,klass in self.tabsfactory:
1676             widget=EditorManager(self.nb, klass)
1677             nb.AddPage(widget,name)
1678             if key==keytoopenon or keytoopenon in key:
1679                 nb.SetSelection(len(self.tabs))
1680             self.tabs.append(widget)
1681         # populate the data
1682         self.Populate()
1683         # and focus on the right one if specified
1684         for _idx, (name,key,klass) in enumerate(self.tabsfactory):
1685             if key and key==keytoopenon and dataindex is not None:
1686                 self.tabs[_idx].SetFocusOnValue(dataindex)
1687 
1688         vs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
1689 
1690         _btn_sizer=wx.StdDialogButtonSizer()
1691         if not readonly:
1692             _btn_sizer.AddButton(wx.Button(self, wx.ID_OK))
1693             if self._data_key is not None:
1694                 _w=wx.Button(self, wx.ID_APPLY)
1695                 self.dirty_widgets[True].append(_w)
1696                 _btn_sizer.AddButton(_w)
1697                 wx.EVT_BUTTON(self, wx.ID_APPLY, self.OnApply)
1698         _btn_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))
1699         _w=wx.Button(self, wx.ID_REVERT_TO_SAVED)
1700         self.dirty_widgets[True].append(_w)
1701         _btn_sizer.SetNegativeButton(_w)
1702         _btn_sizer.Realize()
1703         vs.Add(_btn_sizer, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
1704 
1705         self.SetSizer(vs)
1706 
1707         wx.EVT_BUTTON(self, wx.ID_REVERT_TO_SAVED, self.Revert)
1708         wx.EVT_TOOL(self, self.ID_UP, self.MoveUp)
1709         wx.EVT_TOOL(self, self.ID_DOWN, self.MoveDown)
1710         wx.EVT_TOOL(self, self.ID_ADD, self.Add)
1711         wx.EVT_TOOL(self, self.ID_DELETE, self.Delete)
1712         self.ignore_dirty=False
1713         self.dirty=False
1714         self.setdirty(False)
1715 
1716     def Revert(self, _):
1717         # reload data
1718         self.Populate()
1719         self.setdirty(False)
1720 
1721     def OnDirtyUI(self, _):
1722         self.setdirty()
1723 
1724     def setdirty(self, flg=True):
1725         if self.ignore_dirty:
1726             return
1727         self.dirty=flg
1728         for w in self.dirty_widgets[self.dirty]:
1729             w.Enable(True)
1730         for w in self.dirty_widgets[not self.dirty]:
1731             w.Enable(False)
1732 
1733     def OnApply(self, _):
1734         # Save the current data
1735         self.GetParent().SaveData(self.GetData(), self.GetDataKey())
1736         self.setdirty(False)
1737 
1738     def Populate(self):
1739         # populate various widget with data
1740         self._set_title()
1741         for _idx, (name,key,klass) in enumerate(self.tabsfactory):
1742             if key is None: 
1743                 # the fields are in data, not in data[key]
1744                 self.tabs[_idx].Populate([self.data])
1745             else:
1746                 self.tabs[_idx].Populate(self.data.get(key, {}))
1747 
1748     def GetData(self):
1749         res=self.data
1750         for i in range(len(self.tabsfactory)):
1751             widget=self.nb.GetPage(i)
1752             data=widget.Get()
1753             key=self.tabsfactory[i][1]
1754             if len(data):
1755                 if key is None:
1756                     res.update(data[0])
1757                 else:
1758                     res[key]=data
1759             else:
1760                 # remove the key
1761                 try:
1762                     if key is not None:
1763                         del res[key]
1764                 except KeyError:
1765                     # which may not have existed ...
1766                     pass
1767         return res
1768 
1769     def GetDataKey(self):
1770         return self._data_key
1771             
1772     def MoveUp(self, _):
1773         self.nb.GetPage(self.nb.GetSelection()).Move(-1)
1774         self.setdirty()
1775     
1776     def MoveDown(self, _):
1777         self.nb.GetPage(self.nb.GetSelection()).Move(+1)
1778         self.setdirty()
1779 
1780     def Add(self, _):
1781         self.nb.GetPage(self.nb.GetSelection()).Add()
1782 
1783     def Delete(self, _):
1784         self.nb.GetPage(self.nb.GetSelection()).Delete()
1785         self.setdirty()
1786 
1787     def _set_title(self):
1788         if hasattr(self, '_title'):
1789             self._title.SetLabel(nameparser.getfullname(self.data['names'][0]))
1790 
1791     def OnMoveNext(self, _):
1792         _key,_data=self.GetParent().GetNextEntry(True)
1793         if _data:
1794             self.data=_data
1795             self._data_key=_key
1796             self.Populate()
1797 
1798     def OnMovePrev(self, _):
1799         _key,_data=self.GetParent().GetNextEntry(False)
1800         if _data:
1801             self.data=_data
1802             self._data_key=_key
1803             self.Populate()
1804 
1805 # SingleFieldEditor-------------------------------------------------------------
1806 class SingleFieldEditor(wx.Dialog):
1807     "Edit a single field for a groups of entries"
1808 
1809     ID_DOWN=wx.NewId()
1810     ID_UP=wx.NewId()
1811     ID_ADD=wx.NewId()
1812     ID_DELETE=wx.NewId()
1813 
1814     tabsfactory={
1815         'categories': ("Categories", "categories", CategoryEditor),
1816         'wallpapers': ("Wallpapers", "wallpapers", WallpaperEditor),
1817         'ringtones': ("Ringtones", "ringtones", RingtoneEditor) }
1818 
1819     color_field_name='phonebook'
1820 
1821     def __init__(self, parent, key):
1822         if not self.tabsfactory.has_key(key):
1823             raise KeyError
1824         super(SingleFieldEditor, self).__init__(parent, -1,
1825                                                 "Edit PhoneBook Entry",
1826                                                 size=(740,580),
1827                                                 style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1828         field_color.get_color_info_from_profile(self)
1829 
1830         self._key=key
1831         vs=wx.BoxSizer(wx.VERTICAL)
1832         _hbs=wx.BoxSizer(wx.HORIZONTAL)
1833         _add_btn=wx.BitmapButton(self, wx.NewId(),
1834                                  wx.ArtProvider.GetBitmap(guihelper.ART_ADD_FIELD), name="Prev Item")
1835         _hbs.Add(_add_btn, 0, wx.EXPAND|wx.LEFT, 10)
1836         wx.EVT_BUTTON(self, _add_btn.GetId(), self.Add)
1837         vs.Add(_hbs, 0, wx.ALL|wx.EXPAND, 5)
1838 
1839         self.nb=wx.Notebook(self, -1)
1840         vs.Add(self.nb,1,wx.EXPAND|wx.ALL,5)
1841 
1842         # instantiate the nb widgets
1843         name,key,klass=self.tabsfactory[key]
1844         widget=EditorManager(self.nb, klass)
1845         self.nb.AddPage(widget,name)
1846 
1847         vs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
1848         _btn_sizer=self.CreateButtonSizer(wx.OK|wx.CANCEL)
1849         vs.Add(_btn_sizer, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
1850 
1851         self.SetSizer(vs)
1852 
1853         wx.EVT_TOOL(self, self.ID_UP, self.MoveUp)
1854         wx.EVT_TOOL(self, self.ID_DOWN, self.MoveDown)
1855         wx.EVT_TOOL(self, self.ID_ADD, self.Add)
1856         wx.EVT_TOOL(self, self.ID_DELETE, self.Delete)
1857 
1858     def MoveUp(self, _):
1859         self.nb.GetPage(0).Move(-1)
1860     
1861     def MoveDown(self, _):
1862         self.nb.GetPage(0).Move(+1)
1863 
1864     def Add(self, _):
1865         self.nb.GetPage(0).Add()
1866 
1867     def Delete(self, _):
1868         self.nb.GetPage(0).Delete()
1869 
1870     def GetData(self):
1871         return self.nb.GetPage(0).Get()
1872 
1873 # main--------------------------------------------------------------------------
1874 if __name__=='__main__':
1875 
1876     # data to edit
1877 
1878     data={ 'names': [ { 'full': 'John Smith'}, { 'nickname': 'I Love Testing'} ],
1879            'categories': [ {'category': 'business'}, {'category': 'friend' } ],
1880            # 'emails': [ {'email': 'ex1@example.com'}, {'email': 'ex2@example.net', 'type': 'home'} ],
1881            'urls': [ {'url': 'www.example.com'}, {'url': 'http://www.example.net', 'type': 'home'} ],
1882            'ringtones': [ {'ringtone': 'mi2.mid', 'use': 'call'}, {'ringtone': 'dots.mid', 'use': 'message'}],
1883            'addresses': [ {'type': 'home', 'street': '123 Main Street', 'city': 'Main Town', 'state': 'CA', 'postalcode': '12345'},
1884                           {'type': 'business', 'company': 'Acme Widgets Inc', 'street': '444 Industrial Way', 'street2': 'Square Business Park',
1885                            'city': 'City Of Quality', 'state': 'Northern', 'postalcode': 'GHGJJ-12324', 'country': 'Nations United'}
1886                           ],
1887            'wallpapers': [{'wallpaper': 'pic1.bmp', 'use': 'call'}, {'wallpaper': 'alert.jpg', 'use': 'message'}],
1888            'flags': [ {'secret': True}, {'wierd': 'orange'} ],
1889            'memos': [ {'memo': 'Some stuff about this person " is usually welcome'}, {'memo': 'A second note'}],
1890            'numbers': [ {'number': '123-432-2342', 'type': 'home', 'speeddial': 3}, {'number': '121=+4321/4', 'type': 'fax'}]
1891            }
1892 
1893     app=wx.PySimpleApp()
1894     with guihelper.WXDialogWrapper(Editor(None,data), True):
1895         pass
1896 

Generated by PyXR 0.9.4