1
2
3
4
5
6
7
8
9
10 "Deals with importing and exporting stuff"
11
12
13 from __future__ import with_statement
14 import contextlib
15 import string
16 import re
17 import StringIO
18 import os
19
20
21 import wx
22 import wx.grid
23 import wx.html
24
25
26 from thirdparty import DSV
27
28
29 import common
30 import guihelper
31 import vcard
32 import phonenumber
33 import guiwidgets
34 import nameparser
35 import phonebook
36 import pubsub
37 import guihelper
38 import csv_calendar
39 import vcal_calendar
40 import ical_calendar
41 import gcal_calendar as gcal
42 import playlist
43 import wpl_file
47 res=[]
48
49 res.append( (guihelper.ID_CALENDAR_WIZARD, 'Import Calendar Wizard...',
50 'Import Calendar Wizard', OnCalendarWizard) )
51 res.append( (wx.NewId(), 'Calendar Import Preset...',
52 'Calendar Import Preset...', OnCalendarPreset) )
53 res.append( (wx.NewId(), 'Auto Calendar Import',
54 'Auto Calendar Import',
55 ( (guihelper.ID_AUTOSYNCSETTINGS, 'Settings',
56 'Configure Auto Calendar Import', None),
57 (guihelper.ID_AUTOSYNCEXECUTE, 'Execute',
58 'Perform Auto Calendar Import', None))
59 ))
60
61 res.append( (guihelper.ID_IMPORT_CSV_CONTACTS,"CSV Contacts...", "Import a CSV file for the phonebook", OnFileImportCSVContacts) )
62 res.append( (guihelper.ID_IMPORT_CSV_CALENDAR,"CSV Calendar...", "Import a CSV file for the calendar", OnFileImportCSVCalendar) )
63
64 res.append( (guihelper.ID_IMPORT_VCARDS,"vCards...", "Import vCards for the phonebook", OnFileImportVCards) )
65
66 res.append((guihelper.ID_IMPORT_VCALENDAR,'vCalendar...', 'Import vCalendar data for the calendar', OnFileImportVCal))
67
68 res.append((guihelper.ID_IMPORT_ICALENDAR, 'iCalendar...',
69 'Import iCalendar data for the calendar',
70 OnFileImportiCal))
71
72 res.append((guihelper.ID_IMPORT_GCALENDAR, 'Google Calendar...',
73 'Import Google Calendar data for the calendar',
74 OnFileImportgCal))
75
76 try:
77 import native.outlook
78 res.append( (guihelper.ID_IMPORT_OUTLOOK_CONTACTS,"Outlook Contacts...", "Import Outlook contacts for the phonebook", OnFileImportOutlookContacts) )
79 res.append( (guihelper.ID_IMPORT_OUTLOOK_CALENDAR,"Outlook Calendar...", "Import Outlook calendar for the calendar", OnFileImportOutlookCalendar) )
80 res.append( (guihelper.ID_IMPORT_OUTLOOK_NOTES,"Outlook Notes...", "Import Outlook notes for the memo", OnFileImportOutlookNotes) )
81 res.append( (guihelper.ID_IMPORT_OUTLOOK_TASKS,"Outlook Tasks...", "Import Outlook tasks for the todo", OnFileImportOutlookTasks) )
82 except:
83 pass
84
85 try:
86 import native.evolution
87 res.append( (guihelper.ID_IMPORT_EVO_CONTACTS,"Evolution Contacts...", "Import Evolution contacts for the phonebook", OnFileImportEvolutionContacts) )
88 except ImportError:
89 pass
90
91 res.append( (guihelper.ID_IMPORT_QTOPIA_CONTACTS,"Qtopia Desktop...", "Import Qtopia Desktop contacts for the phonebook", OnFileImportQtopiaDesktopContacts) )
92
93 res.append( (guihelper.ID_IMPORT_GROUPWARE_CONTACTS,"eGroupware...", "Import eGroupware contacts for the phonebook", OnFileImporteGroupwareContacts) )
94
95 res.append( (guihelper.ID_IMPORT_WPL, 'WPL Play List...',
96 'Import WPL Play List',
97 OnWPLImport))
98 return res
99
116
118
119 res=[]
120 res.append({ 'type': 'CSV Calendar',
121 'source': csv_calendar.ImportDataSource,
122 'data': csv_calendar.CSVCalendarImportData })
123 res.append({ 'type': 'vCalendar',
124 'source': vcal_calendar.ImportDataSource,
125 'data': vcal_calendar.VCalendarImportData })
126 res.append({ 'type': 'iCalendar',
127 'source': ical_calendar.ImportDataSource,
128 'data': ical_calendar.iCalendarImportData })
129 res.append({ 'type': 'Google Calendar',
130 'source': gcal.ImportDataSource,
131 'data': gcal.gCalendarImportData })
132 try:
133 import native.outlook
134 import outlook_calendar
135 res.append({ 'type': 'Outlook Calendar',
136 'source': outlook_calendar.ImportDataSource,
137 'data': outlook_calendar.OutlookCalendarImportData })
138 except:
139 pass
140 return res
141
143 import native.outlook
144 try:
145 native.outlook.getmapinamespace()
146 except:
147 guihelper.MessageDialog(None, 'Unable to initialise Outlook, Check that it is installed correctly.',
148 'Outlook Error', wx.OK|wx.ICON_ERROR)
149 return False
150 return True
151
153
155 wx.grid.Grid.__init__(self, parent, id, style=wx.WANTS_CHARS)
156 wx.grid.EVT_GRID_CELL_LEFT_DCLICK(self, self.OnLeftDClick)
157
158
159
160
162 if self.CanEnableCellControl():
163 self.EnableCellEditControl()
164
166 "The dialog for importing phonebook stuff"
167
168
169
170
171 possiblecolumns=["<ignore>", "First Name", "Last Name", "Middle Name",
172 "Name", "Nickname", "Email Address", "Web Page", "Fax", "Home Street",
173 "Home City", "Home Postal Code", "Home State",
174 "Home Country/Region", "Home Phone", "Home Fax", "Mobile Phone", "Home Web Page",
175 "Business Street", "Business City", "Business Postal Code",
176 "Business State", "Business Country/Region", "Business Web Page",
177 "Business Phone", "Business Fax", "Pager", "Company", "Notes", "Private",
178 "Category", "Categories"]
179 bp_columns=[
180
181 'names_title', 'names_first', 'names_middle', 'names_last',
182 'names_full', 'names_nickname',
183 'addresses_type', 'addresses_company', 'addresses_street',
184 'addresses_street2', 'addresses_city',
185 'addresses_state', 'addresses_postalcode',
186 'addresses_country',
187 'numbers_number', 'numbers_type', 'numbers_speeddial',
188 'emails_email', 'emails_type',
189 'urls_url', 'urls_type',
190 'categories_category',
191 'ringtones_ringtone', 'ringtones_use',
192 'wallpapers_wallpaper', 'wallpapers_use',
193 'memos_memo', 'flags_secret'
194 ]
195
196
197
198 filternamecolumns=["First Name", "Last Name", "Middle Name", "Name", "Nickname"]
199
200 filternumbercolumns=["Home Phone", "Home Fax", "Mobile Phone", "Business Phone",
201 "Business Fax", "Pager", "Fax", "Phone"]
202
203 filterhomeaddresscolumns=["Home Street", "Home City", "Home Postal Code", "Home State",
204 "Home Country/Region"]
205
206 filterbusinessaddresscolumns=["Business Street", "Business City",
207 "Business Postal Code", "Business State", "Business Country/Region"]
208
209 filteraddresscolumns=filterhomeaddresscolumns+filterbusinessaddresscolumns+["Address"]
210
211 filteremailcolumns=["Email Address", "Email Addresses"]
212
213
214 addressmap={
215 'Street': 'street',
216 'City': 'city',
217 'Postal Code': 'postalcode',
218 'State': 'state',
219 'Country/Region': 'country',
220 }
221
222 namemap={
223 'First Name': 'first',
224 'Last Name': 'last',
225 'Middle Name': 'middle',
226 'Name': 'full',
227 'Nickname': 'nickname'
228 }
229
230 numbermap={
231 "Home Phone": 'home',
232 "Home Fax": 'fax',
233 "Mobile Phone": 'cell',
234 "Business Phone": 'office',
235 "Business Fax": 'fax',
236 "Pager": 'pager',
237 "Fax": 'fax'
238 }
239
240
241 - def __init__(self, parent, id, title, style=wx.CAPTION|wx.MAXIMIZE_BOX|\
242 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
243 wx.Dialog.__init__(self, parent, id=id, title=title, style=style)
244 self.possiblecolumns+=self.bp_columns
245 self.merge=True
246 vbs=wx.BoxSizer(wx.VERTICAL)
247 t,sz=self.gethtmlhelp()
248 w=wx.html.HtmlWindow(self, -1, size=sz, style=wx.html.HW_SCROLLBAR_NEVER)
249 w.SetPage(t)
250 vbs.Add(w, 0, wx.EXPAND|wx.ALL,5)
251
252 self.getcontrols(vbs)
253
254 cfg=lambda key: wx.GetApp().config.ReadInt("importdialog/filter"+key, False)
255
256
257
258 hbs=wx.BoxSizer(wx.HORIZONTAL)
259 hbs.Add(wx.StaticText(self, -1, "Only rows with "), 0, wx.ALL|wx.ALIGN_CENTRE,2)
260 self.wname=wx.CheckBox(self, wx.NewId(), "a name")
261 self.wname.SetValue(cfg("name"))
262 hbs.Add(self.wname, 0, wx.LEFT|wx.RIGHT|wx.ALIGN_CENTRE,7)
263 self.wnumber=wx.CheckBox(self, wx.NewId(), "a number")
264 self.wnumber.SetValue(cfg("phonenumber"))
265 hbs.Add(self.wnumber, 0, wx.LEFT|wx.RIGHT|wx.ALIGN_CENTRE,7)
266 self.waddress=wx.CheckBox(self, wx.NewId(), "an address")
267 self.waddress.SetValue(cfg("postaladdress"))
268 hbs.Add(self.waddress, 0, wx.LEFT|wx.RIGHT|wx.ALIGN_CENTRE,7)
269 self.wemail=wx.CheckBox(self, wx.NewId(), "an email")
270 self.wemail.SetValue(cfg("emailaddress"))
271 hbs.Add(self.wemail, 0, wx.LEFT|wx.ALIGN_CENTRE,7)
272 cats=wx.GetApp().config.Read("importdialog/filtercategories", "")
273 if len(cats):
274 self.categorieswanted=cats.split(";")
275 else:
276 self.categorieswanted=None
277 self.categoriesbutton=wx.Button(self, wx.NewId(), "Categories...")
278 hbs.Add(self.categoriesbutton, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.ALIGN_CENTRE, 10)
279 self.categorieslabel=wx.StaticText(self, -1, "")
280 if self.categorieswanted is None:
281 self.categorieslabel.SetLabel("*ANY*")
282 else:
283 self.categorieslabel.SetLabel("; ".join(self.categorieswanted))
284 hbs.Add(self.categorieslabel, 1, wx.ALIGN_LEFT|wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, 5)
285 vbs.Add(hbs,0, wx.EXPAND|wx.ALL,5)
286
287 self._name_option=wx.RadioBox(self, -1, 'Name Reformat',
288 choices=['No Reformat', 'First M Last', 'Last, First M'])
289 wx.EVT_RADIOBOX(self, self._name_option.GetId(),
290 self.DataNeedsUpdate)
291 vbs.Add(self._name_option, 0, wx.ALL, 5)
292
293 self.preview=PreviewGrid(self, wx.NewId())
294 self.preview.CreateGrid(10,10)
295 self.preview.SetColLabelSize(0)
296 self.preview.SetRowLabelSize(0)
297 self.preview.SetMargins(1,0)
298
299 vbs.Add(self.preview, 1, wx.EXPAND|wx.ALL, 5)
300
301 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
302 _button_sizer=self.CreateButtonSizer(wx.CANCEL|wx.HELP)
303 _btn=wx.Button(self, -1, 'Merge')
304 _button_sizer.Add(_btn, 0, wx.ALIGN_CENTER|wx.ALL, 5)
305 wx.EVT_BUTTON(self, _btn.GetId(), self.OnOk)
306 _btn=wx.Button(self, -1, 'Replace All')
307 _button_sizer.Add(_btn, 0, wx.ALIGN_CENTER|wx.ALL, 5)
308 wx.EVT_BUTTON(self, _btn.GetId(), self.OnReplaceAll)
309 vbs.Add(_button_sizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
310 self.SetSizer(vbs)
311 for w in self.wname, self.wnumber, self.waddress, self.wemail:
312 wx.EVT_CHECKBOX(self, w.GetId(), self.DataNeedsUpdate)
313
314 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
315 wx.EVT_CLOSE(self, self.OnClose)
316 wx.EVT_BUTTON(self, self.categoriesbutton.GetId(), self.OnCategories)
317
318 guiwidgets.set_size("importdialog", self, 90)
319
320 self.DataNeedsUpdate()
321
323 "The preview data needs to be updated"
324 self.needsupdate=True
325 wx.CallAfter(self.UpdateData)
326
328 "Called when the user has changed one of the columns"
329 self.columns[event.GetCol()]=self.preview.GetCellValue(0, event.GetCol())
330 self.wcolumnsname.SetValue("Custom")
331 if self.wname.GetValue() or self.wnumber.GetValue() or self.waddress.GetValue() or self.wemail.GetValue():
332 self.DataNeedsUpdate()
333
335
336 guiwidgets.save_size("importdialog", self.GetRect())
337 cfg=lambda key, value: wx.GetApp().config.WriteInt("importdialog/filter"+key, value)
338 cfg("name", self.wname.GetValue())
339 cfg("phonenumber", self.wnumber.GetValue())
340 cfg("postaladdress", self.waddress.GetValue())
341 cfg("emailaddress", self.wemail.GetValue())
342 if self.categorieswanted is None:
343 cats=""
344 else:
345 cats=";".join(self.categorieswanted)
346 wx.GetApp().config.Write("importdialog/filtercategories", cats)
347 wx.GetApp().config.Flush()
348 if event is not None:
349 event.Skip()
350
352 "Ok button was pressed"
353 if self.preview.IsCellEditControlEnabled():
354 self.preview.HideCellEditControl()
355 self.preview.SaveEditControlValue()
356 self.OnClose()
357 self.EndModal(wx.ID_OK)
358
360 "ReplaceAll button was pressed"
361 self.merge=False
362 self.OnOk(evt)
363
384 _reformat_name_func=(lambda self, entry: entry,
385 _reformat_name_firstmiddlelast,
386 _reformat_name_lastfirtsmiddle)
387 - def __build_entry(self, rec):
388 entry={}
389
390 emails=[]
391 if rec.has_key('Email Address'):
392 for e in rec['Email Address']:
393 if isinstance(e, dict):
394 emails.append(e)
395 else:
396 emails.append({'email': e})
397 del rec['Email Address']
398 if rec.has_key("Email Addresses"):
399 for e in rec['Email Addresses']:
400 emails.append({'email': e})
401 del rec["Email Addresses"]
402 if len(emails):
403 entry['emails']=emails
404
405 for prefix,fields in \
406 ( ("Home", self.filterhomeaddresscolumns),
407 ("Business", self.filterbusinessaddresscolumns)
408 ):
409 addr={}
410 for k in fields:
411 if k in rec:
412
413 shortk=k[len(prefix)+1:]
414 addr['type']=prefix.lower()
415 addr[self.addressmap[shortk]]=rec[k]
416 del rec[k]
417 if len(addr):
418 if prefix=="Business" and rec.has_key("Company"):
419
420 addr['type']=prefix.lower()
421 addr['company']=rec["Company"]
422 if not entry.has_key("addresses"):
423 entry["addresses"]=[]
424 entry["addresses"].append(addr)
425
426 if rec.has_key("Address"):
427
428 if not entry.has_key("addresses"):
429 entry["addresses"]=[]
430
431 company=rec.get("Company", None)
432 for a in rec["Address"]:
433 if a["type"]=="business": a["company"]=company
434 addr={}
435 for k in ("type", "company", "street", "street2", "city", "state", "postalcode", "country"):
436 v=a.get(k, None)
437 if v is not None: addr[k]=v
438 entry["addresses"].append(addr)
439 del rec["Address"]
440
441 numbers=[]
442 for field in self.filternumbercolumns:
443 if field!="Phone" and rec.has_key(field):
444 for val in rec[field]:
445 numbers.append({'type': self.numbermap[field], 'number': phonenumber.normalise(val)})
446 del rec[field]
447
448 if rec.has_key("Phone"):
449 mapping={"business": "office", "business fax": "fax", "home fax": "fax"}
450 for val in rec["Phone"]:
451 number={"type": mapping.get(val["type"], val["type"]),
452 "number": phonenumber.normalise(val["number"])}
453 sd=val.get('speeddial', None)
454 if sd is not None:
455 number.update({ 'speeddial': sd })
456 numbers.append(number)
457 del rec["Phone"]
458 if len(numbers):
459 entry["numbers"]=numbers
460
461
462 name={}
463 for field in self.filternamecolumns:
464 if field in rec:
465 name[self.namemap[field]]=rec[field]
466 del rec[field]
467 if len(name):
468 entry["names"]=[name]
469
470 if rec.has_key("Notes"):
471 notes=[]
472 for note in rec["Notes"]:
473 notes.append({'memo': note})
474 del rec["Notes"]
475 entry["memos"]=notes
476
477 urls=[]
478 for type, key in ( (None, "Web Page"),
479 ("home", "Home Web Page"),
480 ("business", "Business Web Page")
481 ):
482 if rec.has_key(key):
483 for url in rec[key]:
484 if isinstance(url, dict):
485 u=url
486 else:
487 u={'url': url}
488 if type is not None:
489 u['type']=type
490 urls.append(u)
491 del rec[key]
492 if len(urls):
493 entry["urls"]=urls
494
495 cats=[]
496 if rec.has_key("Category"):
497 cats=rec['Category']
498 del rec["Category"]
499 if rec.has_key("Categories"):
500
501 if isinstance(rec['Categories'], list):
502 cats+=rec['Categories']
503 else:
504 for cat in rec['Categories'].split(';'):
505 cats.append(cat)
506 del rec['Categories']
507 _cats=[]
508 if self.categorieswanted is not None:
509 for c in self.categorieswanted:
510 if c in cats:
511 _cats.append({'category': c })
512 if _cats:
513 entry["categories"]=_cats
514
515 l=[]
516 r=rec.get('Wallpapers', None)
517 if r is not None:
518 if isinstance(r, list):
519 l=[{'wallpaper': x, 'use': 'call' } for x in r]
520 else:
521 l=[{'wallpaper': x, 'use': 'call' } for x in r.split(';')]
522 del rec['Wallpapers']
523 if len(l):
524 entry['wallpapers']=l
525
526 l=[]
527 r=rec.get('Ringtones', None)
528 if r is not None:
529 if isinstance(r, list):
530 l=[{'ringtone': x, 'use': 'call'} for x in r]
531 else:
532 l=[{'ringtone': x, 'use': 'call'} for x in r.split(';')]
533 del rec['Ringtones']
534 if len(l):
535 entry['ringtones']=l
536
537 flags=[]
538 if rec.has_key("Private"):
539 private=True
540
541 if rec["Private"].lower() in ("false", "no", 0, "0"):
542 private=False
543 flags.append({'secret': private})
544 del rec["Private"]
545
546 if len(flags):
547 entry["flags"]=flags
548
549
550 serial={}
551 for k in rec.keys():
552 if k.startswith("UniqueSerial-"):
553 v=rec[k]
554 del rec[k]
555 k=k[len("UniqueSerial-"):]
556 serial[k]=v
557 if len(serial):
558 assert serial.has_key("sourcetype")
559 if len(serial)>1:
560 entry["serials"]=[serial]
561
562
563 if rec.has_key("Company"): del rec["Company"]
564 if len(rec):
565 raise Exception(
566 "Internal conversion failed to complete.\nStill to do: %s" % rec)
567 return entry
568
569 - def __build_bp_entry(self, rec):
570 entry={}
571 for idx,col in enumerate(self.columns):
572
573 key=col[:col.find('_')]
574 field=col[col.find('_')+1:]
575 v=rec[idx]
576 if not len(v):
577 v=None
578 if not entry.has_key(key):
579 entry[key]=[]
580 done=False
581 for field_idx,n in enumerate(entry[key]):
582 if not n.has_key(field):
583 entry[key][field_idx][field]=v
584 done=True
585 break
586 if not done:
587 entry[key].append({ field: v })
588
589 for k,e in entry.items():
590 for i1,d in enumerate(e):
591 for k2,item in d.items():
592 if item is None:
593 del entry[k][i1][k2]
594 else:
595 if k2=='speeddial':
596 d[k2]=int(item)
597 elif k2=='secret':
598 d[k2]=True
599 if item.lower() in ("false", "no", 0, "0"):
600 d[k2]=False
601 l=[x for x in entry[k] if len(x)]
602 if len(l):
603 entry[k]=l
604 else:
605 del entry[k]
606 return entry
607
647
649 res=""
650 for col,name in enumerate(self.columns):
651 if name=="Categories":
652 res+="_getpreviewformatted(row[%d], %s).split(';') + " % (col, `name`)
653 elif name=="Category":
654 res+="_getpreviewformatted(row[%d], %s) + " % (col, `name`)
655 res+="[]"
656 fn=compile(res, "_GetExtractCategoriesFunction_", 'eval')
657 return lambda row: eval(fn, globals(), {'row': row})
658
659
661
662 savedcolumns,saveddata=self.columns, self.data
663 if self.categorieswanted is not None:
664
665
666 self.ReReadData()
667 catfn=self.GetExtractCategoriesFunction()
668 cats=[]
669 for row in self.data:
670 for c in catfn(row):
671 if c not in cats:
672 cats.append(c)
673 cats.sort()
674 if len(cats) and cats[0]=="":
675 cats=cats[1:]
676 self.columns,self.data=savedcolumns, saveddata
677 with guihelper.WXDialogWrapper(CategorySelectorDialog(self, self.categorieswanted, cats),
678 True) as (dlg, retcode):
679 if retcode==wx.ID_OK:
680 self.categorieswanted=dlg.GetCategories()
681 if self.categorieswanted is None:
682 self.categorieslabel.SetLabel("*ALL*")
683 else:
684 self.categorieslabel.SetLabel("; ".join(self.categorieswanted))
685 self.DataNeedsUpdate()
686
687 @guihelper.BusyWrapper
738
774 _preview_format_names_func=(_preview_format_name_none,
775 _preview_format_name_firstmiddlelast,
776 _preview_format_name_lastfirtmiddle)
777
779 self.preview.BeginBatch()
780 if self.preview.GetNumberCols():
781 self.preview.DeleteCols(0,self.preview.GetNumberCols())
782 self.preview.DeleteRows(0,self.preview.GetNumberRows())
783 self.preview.ClearGrid()
784
785 numrows=len(self.data)
786 if numrows:
787 numcols=max(map(lambda x: len(x), self.data))
788 else:
789 numcols=len(self.columns)
790
791 editor=wx.grid.GridCellChoiceEditor(self.possiblecolumns, False)
792 self.preview.AppendRows(1)
793 self.preview.AppendCols(numcols)
794 _names_col={}
795 for col in range(numcols):
796 if 'Name' in self.columns[col] or \
797 'names_' in self.columns[col]:
798 _names_col[self.columns[col]]=col
799 self.preview.SetCellValue(0, col, self.columns[col])
800 self.preview.SetCellEditor(0, col, editor)
801 attr=wx.grid.GridCellAttr()
802 attr.SetBackgroundColour(wx.GREEN)
803 attr.SetFont(wx.Font(10,wx.SWISS, wx.NORMAL, wx.BOLD))
804 attr.SetReadOnly(not self.headerrowiseditable)
805 self.preview.SetRowAttr(0,attr)
806
807 oddattr=wx.grid.GridCellAttr()
808 oddattr.SetBackgroundColour("OLDLACE")
809 oddattr.SetReadOnly(True)
810 evenattr=wx.grid.GridCellAttr()
811 evenattr.SetBackgroundColour("ALICE BLUE")
812 evenattr.SetReadOnly(True)
813 _format_name=self._preview_format_names_func[self._name_option.GetSelection()]
814 for row in range(numrows):
815 self.preview.AppendRows(1)
816 for col in range(numcols):
817 if self.columns[col] in ('Name', 'names_full'):
818 s=_format_name(self, self.data[row], col, _names_col)
819 else:
820 s=_getpreviewformatted(self.data[row][col], self.columns[col])
821 if len(s):
822 self.preview.SetCellValue(row+1, col, s)
823 self.preview.SetRowAttr(row+1, (evenattr,oddattr)[row%2])
824 self.preview.AutoSizeColumns()
825 self.preview.AutoSizeRows()
826 self.preview.EndBatch()
827
858
861
862 - def __init__(self, parent, categorieswanted, categoriesavailable):
863 wx.Dialog.__init__(self, parent, title="Import Category Selector", style=wx.CAPTION|wx.MAXIMIZE_BOX|\
864 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
865 vbs=wx.BoxSizer(wx.VERTICAL)
866 hbs=wx.BoxSizer(wx.HORIZONTAL)
867 self.selected=wx.RadioButton(self, wx.NewId(), "Selected Below", style=wx.RB_GROUP)
868 self.any=wx.RadioButton(self, wx.NewId(), "Any/All")
869 hbs.Add(self.selected, 0, wx.ALL, 5)
870 hbs.Add(self.any, 0, wx.ALL, 5)
871 _up=wx.BitmapButton(self, -1,
872 wx.ArtProvider.GetBitmap(guihelper.ART_ARROW_UP, wx.ART_TOOLBAR,
873 wx.Size(16, 16)))
874 _dn=wx.BitmapButton(self, -1,
875 wx.ArtProvider.GetBitmap(guihelper.ART_ARROW_DOWN, wx.ART_TOOLBAR,
876 wx.Size(16, 16)))
877 hbs.Add(_up, 0, wx.ALL, 5)
878 wx.EVT_BUTTON(self, _up.GetId(), self.OnMoveUp)
879 wx.EVT_BUTTON(self, _dn.GetId(), self.OnMoveDown)
880 hbs.Add(_dn, 0, wx.ALL, 5)
881 vbs.Add(hbs, 0, wx.ALL, 5)
882
883 self.categoriesavailable=categoriesavailable
884 self.cats=wx.CheckListBox(self, wx.NewId(), choices=categoriesavailable)
885 vbs.Add(self.cats, 1, wx.EXPAND|wx.ALL, 5)
886 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
887 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
888
889 if categorieswanted is None:
890 self.any.SetValue(True)
891 self.selected.SetValue(False)
892 else:
893 self.any.SetValue(False)
894 self.selected.SetValue(True)
895 for c in categorieswanted:
896 try:
897 self.cats.Check(categoriesavailable.index(c))
898 except ValueError:
899 pass
900
901 wx.EVT_CHECKLISTBOX(self, self.cats.GetId(), self.OnCatsList)
902
903 self.SetSizer(vbs)
904 vbs.Fit(self)
905
909
911 if self.any.GetValue():
912 return None
913 return [self.cats.GetString(x) for x in range(len(self.categoriesavailable)) if self.cats.IsChecked(x)]
914
916 _sel_str=self.cats.GetStringSelection()
917 _chk=self.GetCategories()
918 if _chk is None:
919 _chk=[]
920 self.cats.Clear()
921 for s in self.categoriesavailable:
922 i=self.cats.Append(s)
923 if s==_sel_str:
924 self.cats.SetSelection(i)
925 self.cats.Check(i, s in _chk)
926
928 _sel_idx=self.cats.GetSelection()
929 if _sel_idx==wx.NOT_FOUND or not _sel_idx:
930
931 return
932
933 self.categoriesavailable[_sel_idx], self.categoriesavailable[_sel_idx-1]=\
934 self.categoriesavailable[_sel_idx-1], self.categoriesavailable[_sel_idx]
935 self._populate()
936
938 _sel_idx=self.cats.GetSelection()
939 if _sel_idx==wx.NOT_FOUND or \
940 _sel_idx==len(self.categoriesavailable)-1:
941
942 return
943
944 self.categoriesavailable[_sel_idx], self.categoriesavailable[_sel_idx+1]=\
945 self.categoriesavailable[_sel_idx+1], self.categoriesavailable[_sel_idx]
946 self._populate()
947
949
950 delimiternames={
951 '\t': "Tab",
952 ' ': "Space",
953 ',': "Comma"
954 }
955
956 - def __init__(self, filename, parent, id, title):
961
963 "Returns tuple of help text and size"
964 bg=self.GetBackgroundColour()
965 return '<html><body BGCOLOR="#%02X%02X%02X">Importing %s. BitPim has guessed the delimiter seperating each column, and the text qualifier that quotes values. You need to select what each column is by clicking in the top row, or select one of the predefined sets of columns.</body></html>' % (bg.Red(), bg.Green(), bg.Blue(), self.filename), \
966 (600,100)
967
969 data=common.opentextfile(self.filename).read()
970
971 data=data.replace("\r", "\n")
972 oldlen=-1
973 while len(data)!=oldlen:
974 oldlen=len(data)
975 data=data.replace("\n\n", "\n")
976
977 self.rawdata=data
978
979 self.qualifier=DSV.guessTextQualifier(self.rawdata)
980 if self.qualifier is None or len(self.qualifier)==0:
981 self.qualifier='"'
982 self.data=DSV.organizeIntoLines(self.rawdata, textQualifier=self.qualifier)
983 self.delimiter=DSV.guessDelimiter(self.data)
984
985 if self.delimiter is not None and self.delimiter.lower() in "abcdefghijklmnopqrstuvwxyz":
986 self.delimiter=None
987 if self.delimiter is None:
988 if self.filename.lower().endswith("tsv"):
989 self.delimiter="\t"
990 else:
991 self.delimiter=","
992
993 self.data=DSV.importDSV(self.data, delimiter=self.delimiter, textQualifier=self.qualifier, errorHandler=DSV.padRow)
994
995 hbs=wx.BoxSizer(wx.HORIZONTAL)
996 hbs.Add(wx.StaticText(self, -1, "Delimiter"), 0, wx.EXPAND|wx.ALL|wx.ALIGN_CENTRE, 2)
997 self.wdelimiter=wx.ComboBox(self, wx.NewId(), self.PrettyDelimiter(self.delimiter), choices=self.delimiternames.values(), style=wx.CB_DROPDOWN|wx.WANTS_CHARS)
998 hbs.Add(self.wdelimiter, 1, wx.EXPAND|wx.ALL, 2)
999 hbs.Add(wx.StaticText(self, -1, "Text Qualifier"), 0, wx.EXPAND|wx.ALL|wx.ALIGN_CENTRE,2)
1000 self.wqualifier=wx.ComboBox(self, wx.NewId(), self.qualifier, choices=['"', "'", "(None)"], style=wx.CB_DROPDOWN|wx.WANTS_CHARS)
1001 hbs.Add(self.wqualifier, 1, wx.EXPAND|wx.ALL, 2)
1002 vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
1003
1004 hbs=wx.BoxSizer(wx.HORIZONTAL)
1005 hbs.Add(wx.StaticText(self, -1, "Columns"), 0, wx.EXPAND|wx.ALL|wx.ALIGN_CENTRE, 2)
1006 self.wcolumnsname=wx.ComboBox(self, wx.NewId(), "Custom", choices=self.predefinedcolumns+["Custom"], style=wx.CB_READONLY|wx.CB_DROPDOWN|wx.WANTS_CHARS)
1007 hbs.Add(self.wcolumnsname, 1, wx.EXPAND|wx.ALL, 2)
1008 self.wfirstisheader=wx.CheckBox(self, wx.NewId(), "First row is header")
1009 self.wfirstisheader.SetValue(DSV.guessHeaders(self.data))
1010 hbs.Add(self.wfirstisheader, 0, wx.EXPAND|wx.ALL|wx.ALIGN_CENTRE, 5)
1011 vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
1012
1013
1014 wx.EVT_CHECKBOX(self, self.wfirstisheader.GetId(), self.OnHeaderToggle)
1015 wx.grid.EVT_GRID_CELL_CHANGE(self, self.OnGridCellChanged)
1016 wx.EVT_TEXT(self, self.wdelimiter.GetId(), self.OnDelimiterChanged)
1017 wx.EVT_TEXT(self, self.wqualifier.GetId(), self.OnQualifierChanged)
1018 wx.EVT_TEXT(self, self.wcolumnsname.GetId(), self.OnColumnsNameChanged)
1019
1021 "Returns a pretty version of the delimiter (eg Tab, Space instead of \t, ' ')"
1022 assert delim is not None
1023 if delim in self.delimiternames:
1024 return self.delimiternames[delim]
1025 return delim
1026
1028 """Updates the list of pre-defined column names.
1029
1030 We look for files with an extension of .pdc in the resource directory. The first
1031 line of the file is the description, and each remaining line corresponds to a
1032 column"""
1033 self.predefinedcolumns=[]
1034 for i in guihelper.getresourcefiles("*.pdc"):
1035 with contextlib.closing(common.opentextfile(i)) as f:
1036 self.predefinedcolumns.append(f.readline().strip())
1037
1041
1069
1071 "Called when the user has changed the qualifier"
1072
1073 text=self.wqualifier.GetValue()
1074 if hasattr(self, "lastwqualifiervalue") and self.lastwqualifiervalue==text:
1075 return
1076 if len(text)!=1:
1077 if text=='(None)':
1078 text=None
1079 else:
1080 if len(text)==0:
1081 self.wqualifier.SetValue('(None)')
1082 text=None
1083 else:
1084 text=text[-1]
1085 self.wqualifier.SetValue(text)
1086 self.qualifier=text
1087 self.columns=None
1088 self.DataNeedsUpdate()
1089 self.lastwqualifiervalue=self.wqualifier.GetValue()
1090 wx.CallAfter(self.wqualifier.SetInsertionPointEnd)
1091 wx.CallAfter(self.wqualifier.SetMark, 0,len(self.wqualifier.GetValue()))
1092
1109
1114
1148
1151
1152 importmapping=(
1153
1154
1155 ('FirstName', "First Name" ),
1156 ('LastName', "Last Name"),
1157 ('MiddleName', "Middle Name"),
1158
1159
1160 ('Subject', "Name"),
1161
1162 ('NickName', "Nickname"),
1163 ('Email1Address', "Email Address"),
1164 ('Email2Address', "Email Address"),
1165 ('Email3Address', "Email Address"),
1166
1167
1168 ('WebPage', "Web Page"),
1169 ('OtherFaxNumber', "Fax" ),
1170 ('HomeAddressStreet', "Home Street"),
1171 ('HomeAddressCity', "Home City" ),
1172 ('HomeAddressPostalCode',"Home Postal Code" ),
1173 ('HomeAddressState', "Home State"),
1174 ('HomeAddressCountry', "Home Country/Region" ),
1175 ('HomeTelephoneNumber', "Home Phone"),
1176 ('Home2TelephoneNumber', "Home Phone"),
1177 ('HomeFaxNumber', "Home Fax"),
1178 ('MobileTelephoneNumber',"Mobile Phone"),
1179 ('PersonalHomePage', "Home Web Page"),
1180
1181 ('BusinessAddressStreet',"Business Street"),
1182 ('BusinessAddressCity', "Business City"),
1183 ('BusinessAddressPostalCode', "Business Postal Code"),
1184 ('BusinessAddressState', "Business State"),
1185 ('BusinessAddressCountry', "Business Country/Region"),
1186
1187 ('BusinessTelephoneNumber', "Business Phone"),
1188 ('Business2TelephoneNumber',"Business Phone"),
1189 ('BusinessFaxNumber', "Business Fax"),
1190 ('PagerNumber', "Pager"),
1191 ('CompanyName', "Company"),
1192
1193 ('Body', "Notes"),
1194
1195 ('Categories', "Categories"),
1196
1197
1198 ('EntryID', "UniqueSerial-EntryID"),
1199
1200 )
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289 importmappingdict={}
1290 for o,i in importmapping: importmappingdict[o]=i
1291
1292 - def __init__(self, parent, id, title, outlook):
1296
1298 "Returns tuple of help text and size"
1299 bg=self.GetBackgroundColour()
1300 return '<html><body BGCOLOR="#%02X%02X%02X">Importing Outlook Contacts. Select the folder to import, and do any filtering necessary.</body></html>' % (bg.Red(), bg.Green(), bg.Blue()), \
1301 (600,30)
1302
1304 hbs=wx.BoxSizer(wx.HORIZONTAL)
1305
1306 hbs.Add(wx.StaticText(self, -1, "Folder"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
1307
1308 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
1309 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
1310
1311 self.folderbrowse=wx.Button(self, wx.NewId(), "Browse ...")
1312 hbs.Add(self.folderbrowse, 0, wx.EXPAND|wx.ALL, 2)
1313 vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
1314 wx.EVT_BUTTON(self, self.folderbrowse.GetId(), self.OnBrowse)
1315
1316
1317 id=wx.GetApp().config.Read("outlook/contacts", "")
1318 self.folder=self.outlook.getfolderfromid(id, True)
1319 wx.GetApp().config.Write("outlook/contacts", self.outlook.getfolderid(self.folder))
1320 self.folderctrl.SetValue(self.outlook.getfoldername(self.folder))
1321
1329
1364
1367 keymapper={
1368 "name": "Name",
1369 "notes": "Notes",
1370 "uid": "UniqueSerial-uid",
1371 "last name": "Last Name",
1372 "first name": "First Name",
1373 "middle name": "Middle Name",
1374 "nickname": "Nickname",
1375 "categories": "Categories",
1376 "email": "Email Address",
1377 "url": "Web Page",
1378 "phone": "Phone",
1379 "address": "Address",
1380 "organisation": "Company",
1381 "wallpapers": "Wallpapers",
1382 "ringtones": "Ringtones"
1383 }
1384 - def __init__(self, filename, parent, id, title):
1389
1391 "Returns tuple of help text and size"
1392 bg=self.GetBackgroundColour()
1393 return '<html><body BGCOLOR="#%02X%02X%02X">Importing vCard Contacts. Verify the data and perform any filtering necessary.</body></html>' % (bg.Red(), bg.Green(), bg.Blue()), \
1394 (600,30)
1395
1399
1401 if self.vcardcolumns is None or self.vcarddata is None:
1402 self.vcardcolumns,self.vcarddata=self.parsevcards(common.opentextfile(self.filename))
1403 self.columns=self.vcardcolumns
1404 self.data=self.vcarddata
1405
1447
1448
1449
1450 _preferredorder=["first name", "middle name", "last name", "name", "nickname",
1451 "phone", "address", "email", "url", "organisation", "categories", "notes"]
1452
1454 po=self._preferredorder
1455
1456 def funkycmpfunc(x, y, po=po):
1457 x=_getstringbase(x)
1458 y=_getstringbase(y)
1459 if x==y: return 0
1460 if x[0]==y[0]:
1461 return cmp(x[1], y[1])
1462
1463
1464
1465
1466
1467 try:
1468 pos1=po.index(x[0])
1469 except ValueError: pos1=-1
1470 try:
1471 pos2=po.index(y[0])
1472 except ValueError: pos2=-1
1473
1474 if pos1<0 and pos2<0: return cmp(x[0], y[0])
1475 if pos1<0 and pos2>=0: return 1
1476 if pos2<0 and pos1>=0: return -1
1477 assert pos1>=0 and pos2>=0
1478 return cmp(pos1, pos2)
1479
1480 keys.sort(funkycmpfunc)
1481
1484 mo=re.match(r"^(.*?)(\d+)$", v)
1485 if mo is None: return (v,1)
1486 return mo.group(1), int(mo.group(2))
1487
1489 - def __init__(self, parent, id, title, evolution):
1495
1497 "Returns tuple of help text and size"
1498 bg=self.GetBackgroundColour()
1499 return '<html><body BGCOLOR="#%02X%02X%02X">Importing Evolution Contacts. Select the folder to import, and do any filtering necessary.</body></html>' % (bg.Red(), bg.Green(), bg.Blue()), \
1500 (600,30)
1501
1503 hbs=wx.BoxSizer(wx.HORIZONTAL)
1504
1505 hbs.Add(wx.StaticText(self, -1, "Folder"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
1506
1507 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY)
1508 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2)
1509
1510 self.folderbrowse=wx.Button(self, wx.NewId(), "Browse ...")
1511 hbs.Add(self.folderbrowse, 0, wx.EXPAND|wx.ALL, 2)
1512 vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
1513 wx.EVT_BUTTON(self, self.folderbrowse.GetId(), self.OnBrowse)
1514
1515
1516 id=wx.GetApp().config.Read("evolution/contacts", "")
1517 self.folder=self.evolution.getfolderfromid(id, True)
1518 print "folder is",self.folder
1519 wx.GetApp().config.Write("evolution/contacts", self.evolution.getfolderid(self.folder))
1520 self.folderctrl.SetValue(self.evolution.getfoldername(self.folder))
1521
1531
1533 if self.evocolumns is not None and self.evodata is not None:
1534 self.columns=self.evocolumns
1535 self.data=self.evodata
1536 return
1537
1538 vcards="\r\n".join(self.evolution.getcontacts(self.folder))
1539
1540 columns,data=self.parsevcards(StringIO.StringIO(vcards))
1541
1542 columns.append("UniqueSerial-folderid")
1543 columns.append("UniqueSerial-sourcetype")
1544 moredata=[self.folder, "evolution"]
1545
1546 for row in data:
1547 row.extend(moredata)
1548
1549 self.evocolumns=self.columns=columns
1550 self.evodata=self.data=data
1551
1553
1554 importmapping=(
1555
1556
1557 ('FirstName', "First Name" ),
1558 ('LastName', "Last Name" ),
1559 ('MiddleName', "Middle Name"),
1560 ('Nickname', "Nickname"),
1561 ('Emails', "Email Addresses"),
1562 ('HomeStreet', "Home Street"),
1563 ('HomeCity', "Home City"),
1564 ('HomeZip', "Home Postal Code"),
1565 ('HomeState', "Home State" ),
1566 ('HomeCountry', "Home Country/Region" ),
1567 ('HomePhone', "Home Phone" ),
1568 ('HomeFax', "Home Fax" ),
1569 ('HomeMobile', "Mobile Phone" ),
1570 ('BusinessMobile', "Mobile Phone" ),
1571 ('HomeWebPage', "Home Web Page" ),
1572 ('BusinessStreet', "Business Street"),
1573 ('BusinessCity', "Business City" ),
1574 ('BusinessZip', "Business Postal Code" ),
1575 ('BusinessState', "Business State" ),
1576 ('BusinessCountry', "Business Country/Region", ),
1577 ('BusinessWebPage', "Business Web Page"),
1578 ('BusinessPhone', "Business Phone"),
1579 ('BusinessFax', "Business Fax" ),
1580 ('BusinessPager', "Pager" ),
1581 ('Company', "Company" ),
1582 ('Notes', "Notes" ),
1583 ('Categories', "Categories" ),
1584 ('Uid', "UniqueSerial-uid" ),
1585
1586 )
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605 importmappingdict={}
1606 for o,i in importmapping: importmappingdict[o]=i
1607
1608 - def __init__(self, parent, id, title):
1609 self.headerrowiseditable=False
1610 self.origcolumns=self.origdata=None
1611 ImportDialog.__init__(self, parent, id, title)
1612
1614 "Returns tuple of help text and size"
1615 bg=self.GetBackgroundColour()
1616 return '<html><body BGCOLOR="#%02X%02X%02X">Importing Qtopia Desktop Contacts..</body></html>' % (bg.Red(), bg.Green(), bg.Blue()), \
1617 (600,30)
1618
1621
1623 if self.origcolumns is not None and self.origdata is not None:
1624 self.columns=self.origcolumns
1625 self.data=self.origdata
1626 return
1627
1628 import native.qtopiadesktop
1629
1630 filename=native.qtopiadesktop.getfilename()
1631 if not os.path.isfile(filename):
1632 wx.MessageBox(filename+" not found.", "Qtopia file not found", wx.ICON_EXCLAMATION|wx.OK)
1633 self.data={}
1634 self.columns=[]
1635 return
1636
1637 items=native.qtopiadesktop.getcontacts()
1638
1639
1640 keys={}
1641 for item in items:
1642 for k in item.keys():
1643 keys[k]=1
1644
1645
1646
1647 want=[]
1648 for o,i in self.importmapping:
1649 if o in keys.keys():
1650 want.append(o)
1651
1652
1653 self.columns=[self.importmappingdict[k] for k in want]
1654
1655 self.columns.append("UniqueSerial-sourcetype")
1656 moredata=[ "qtopiadesktop"]
1657
1658
1659 self.data=[]
1660 for item in items:
1661 row=[]
1662 for k in want:
1663 v=item.get(k, None)
1664 row.append(v)
1665 self.data.append(row+moredata)
1666
1667 self.origdata=self.data
1668 self.origcolumns=self.columns
1669
1673
1674 __pwdsentinel="\x01\x02\x01\x09\x01\x01\x14\x15"
1675
1676
1677 - def __init__(self, parent, module, title="Login to eGroupware"):
1678 wx.Dialog.__init__(self, parent, -1, title, size=(400,-1))
1679 self.module=module
1680 gs=wx.GridBagSizer(5,5)
1681 for row,label in enumerate( ("URL", "Domain", "Username", "Password") ):
1682 gs.Add(wx.StaticText(self, -1, label), flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL, pos=(row,0))
1683 self.curl=wx.TextCtrl(self, -1)
1684 self.cdomain=wx.TextCtrl(self, -1)
1685 self.cuser=wx.TextCtrl(self, -1)
1686 self.cpassword=wx.TextCtrl(self, -1, style=wx.TE_PASSWORD)
1687 self.csavepassword=wx.CheckBox(self, -1, "Save")
1688 for row,widget in enumerate( (self.curl, self.cdomain, self.cuser) ):
1689 gs.Add(widget, flag=wx.EXPAND, pos=(row,1), span=(1,2))
1690 gs.Add(self.cpassword, flag=wx.EXPAND, pos=(3,1))
1691 gs.Add(self.csavepassword, flag=wx.ALIGN_CENTRE, pos=(3,2))
1692 gs.AddGrowableCol(1)
1693 self.cmessage=wx.StaticText(self, -1, "Please enter your details")
1694 gs.Add(self.cmessage, flag=wx.EXPAND, pos=(4,0), span=(1,3))
1695 vbs=wx.BoxSizer(wx.VERTICAL)
1696 vbs.Add(gs, 0, wx.EXPAND|wx.ALL,5)
1697 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
1698 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
1699
1700
1701 cfg=wx.GetApp().config
1702 self.curl.SetValue(cfg.Read("egroupware/url", "http://server.example.com/egroupware"))
1703 self.cdomain.SetValue(cfg.Read("egroupware/domain", "default"))
1704 try:
1705 import getpass
1706 defuser=getpass.getuser()
1707 except:
1708 defuser="user"
1709 self.cuser.SetValue(cfg.Read("egroupware/user", defuser))
1710 p=cfg.Read("egroupware/password", "")
1711 if len(p):
1712 self.csavepassword.SetValue(True)
1713 self.cpassword.SetValue(self.__pwdsentinel)
1714
1715 self.SetSizer(vbs)
1716 self.SetAutoLayout(True)
1717 vbs.Fit(self)
1718
1719 self.SetDimensions(-1, -1, 500, -1, wx.SIZE_USE_EXISTING)
1720 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
1721 self.session=None
1722
1723 - def OnOk(self, evt):
1731
1732
1734 """Returns the Session object from the eGroupware library
1735 @param auto: If true then the user interface doesn't have to be shown"""
1736
1737 if auto:
1738 try:
1739 self.session=self._GetSession()
1740 return self.session
1741 except Exception,e:
1742 self.cmessage.SetLabel(str(e))
1743
1744 self.ShowModal()
1745 return self.session
1746
1760
1772
1776
1777 ID_CHANGE=wx.NewId()
1778
1779 - def __init__(self, parent, id, title, module):
1780 self.headerrowiseditable=False
1781 self.module=module
1782 ImportDialog.__init__(self, parent, id, title)
1783 self.sp=None
1784
1786 "Returns tuple of help text and size"
1787 bg=self.GetBackgroundColour()
1788 return '<html><body BGCOLOR="#%02X%02X%02X">Importing eGroupware Contacts..</body></html>' % (bg.Red(), bg.Green(), bg.Blue()), \
1789 (600,30)
1790
1792
1793 hbs=wx.BoxSizer(wx.HORIZONTAL)
1794 hbs.Add(wx.StaticText(self, -1, "URL"), 0, wx.ALIGN_CENTRE|wx.ALL,2)
1795 self.curl=wx.StaticText(self, -1)
1796 hbs.Add(self.curl, 3, wx.ALIGN_CENTRE_VERTICAL|wx.ALL, 2)
1797 hbs.Add(wx.StaticText(self, -1, "Domain"), 0, wx.ALIGN_CENTRE|wx.ALL,2)
1798 self.cdomain=wx.StaticText(self, -1)
1799 hbs.Add(self.cdomain, 1, wx.ALIGN_CENTRE_VERTICAL|wx.ALL, 2)
1800 hbs.Add(wx.StaticText(self, -1, "User"), 0, wx.ALIGN_CENTRE|wx.ALL,2)
1801 self.cuser=wx.StaticText(self, -1)
1802 hbs.Add(self.cuser, 1, wx.ALIGN_CENTRE_VERTICAL|wx.ALL, 2)
1803 self.cchange=wx.Button(self, self.ID_CHANGE, "Change ...")
1804 hbs.Add(self.cchange, 0, wx.ALL, 2)
1805 vbs.Add(hbs,0,wx.ALL,5)
1806 wx.EVT_BUTTON(self, self.ID_CHANGE, self.OnChangeCreds)
1807 self.setcreds()
1808
1816
1818 cfg=wx.GetApp().config
1819 self.curl.SetLabel(cfg.Read("egroupware/url", "http://server.example.com/egroupware"))
1820 self.cdomain.SetLabel(cfg.Read("egroupware/domain", "default"))
1821 try:
1822 import getpass
1823 defuser=getpass.getuser()
1824 except:
1825 defuser="user"
1826 self.cuser.SetLabel(cfg.Read("egroupware/user", defuser))
1827
1828 _preferred=( "Name", "First Name", "Middle Name", "Last Name",
1829 "Address", "Address2", "Email Address", "Email Address2",
1830 "Home Phone", "Mobile Phone", "Business Fax", "Pager", "Business Phone",
1831 "Notes", "Business Web Page", "Categories" )
1832
1834 if self.sp is None:
1835 self.sp=eGroupwareLoginDialog(self, self.module).GetSession(auto=True)
1836 self.setcreds()
1837 self.data=[]
1838 self.columns=[]
1839 if self.sp is None:
1840 self.EndModal(wx.ID_CANCEL)
1841 return
1842
1843 entries=[i for i in self.sp.getcontactspbformat()]
1844 cols=[]
1845 for e in entries:
1846 for k in e:
1847 if k not in cols:
1848 cols.append(k)
1849
1850 cols.sort()
1851 self.columns=[]
1852 for p in self._preferred:
1853 if p in cols:
1854 self.columns.append(p)
1855 cols=[c for c in cols if c not in self.columns]
1856 self.columns.extend(cols)
1857
1858 self.data=[]
1859 for e in entries:
1860 self.data.append([e.get(c,None) for c in self.columns])
1861
1862 for i,c in enumerate(self.columns):
1863 if c.endswith("2"):
1864 self.columns[i]=c[:-1]
1865
1883
1885 with guihelper.WXDialogWrapper(wx.FileDialog(parent, "Import vCards file",
1886 wildcard="vCard files (*.vcf)|*.vcf|All files|*",
1887 style=wx.OPEN|wx.HIDE_READONLY|wx.CHANGE_DIR),
1888 True) as (dlg, retcode):
1889 if retcode==wx.ID_OK:
1890 path=dlg.GetPath()
1891 else:
1892 return
1893
1894 with guihelper.WXDialogWrapper(ImportVCardDialog(path, parent, -1, "Import vCard file"),
1895 True) as (dlg, retcode):
1896 if retcode==wx.ID_OK:
1897 data=dlg.GetFormattedData()
1898 if data is not None:
1899 parent.GetActivePhonebookWidget().importdata(data, merge=dlg.merge)
1900
1908
1920
1929
1938
1967
1978
1985
1991
2001
2011
2016
2021
2027
2032
2047
2055
2062
2064 with guihelper.WXDialogWrapper(dlg, True) as (dlg, res):
2065 if res==wx.ID_OK:
2066 config=(dlg.GetFolder(), dlg.GetFilter())
2067 else:
2068 config=()
2069 return res, config
2070
2078
2083
2088
2090 parent.calendarwidget.populate(calendar_r)
2091 parent.calendarwidget.populatefs(calendar_r)
2092 res=1
2093 return res
2094
2097
2100
2123
2129 res=[]
2130
2131 res.append( (guihelper.ID_EXPORT_VCARD_CONTACTS, "vCards...", "Export the phonebook to vCards", OnFileExportVCards) )
2132
2133 res.append( (guihelper.ID_EXPORT_GROUPWARE_CONTACTS, "eGroupware...", "Export the phonebook to eGroupware", OnFileExporteGroupware) )
2134
2135 res.append( (guihelper.ID_EXPORT_CSV_CONTACTS, 'CSV Contacts...', 'Export the phonebook to CSV', OnFileExportCSV))
2136 res.append( (guihelper.ID_EXPORT_CSV_CALENDAR, 'CSV Calendar...', 'Export the calendar to CSV', OnFileExportCSVCalendar) )
2137
2138 res.append( (guihelper.ID_EXPORT_ICALENDAR,
2139 'iCalendar...',
2140 'Export the calendar to iCalendar',
2141 OnFileExportiCalendar) )
2142
2143 res.append( (guihelper.ID_EXPORT_SMS, 'SMS...', 'Export SMS Messages', OnFileExportSMS))
2144
2145 res.append( (guihelper.ID_EXPORT_CSV_CALL_HISTORY, 'CSV Call History...', 'Export Call History to CSV',
2146 OnFileExportCallHistory))
2147
2148 res.append( (guihelper.ID_EXPORT_MEDIA_TO_DIR, 'Media to Folder...', 'Export Media Files to a Folder on your computer',
2149 OnFileExportMediaDir))
2150 res.append( (guihelper.ID_EXPORT_MEDIA_TO_ZIP, 'Media to Zip File...', 'Export Media Files to a Zip file',
2151 OnFileExportMediaZip))
2152 return res
2153
2155
2156 - def __init__(self, parent, title, style=wx.CAPTION|wx.MAXIMIZE_BOX|\
2157 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE):
2160
2162 "Returns a sizer containing the gui for selecting which items and which fields are exported"
2163 hbs=wx.BoxSizer(wx.HORIZONTAL)
2164
2165 lsel=len(self._phonebook_module.GetSelectedRows())
2166 lall=len(self._phonebook_module._data)
2167 rbs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Rows"), wx.VERTICAL)
2168 self.rows_selected=wx.RadioButton(self, wx.NewId(), "Selected (%d)" % (lsel,), style=wx.RB_GROUP)
2169 self.rows_all=wx.RadioButton(self, wx.NewId(), "All (%d)" % (lall,))
2170 rbs.Add(self.rows_selected, 0, wx.EXPAND|wx.ALL, 2)
2171 rbs.Add(self.rows_all, 0, wx.EXPAND|wx.ALL, 2)
2172 hbs.Add(rbs, 3, wx.EXPAND|wx.ALL, 5)
2173 self.rows_selected.SetValue(lsel>1)
2174 self.rows_all.SetValue(not lsel>1)
2175
2176 vbs2=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Fields"), wx.VERTICAL)
2177 cb=[]
2178 for c in ("Everything", "Phone Numbers", "Addresses", "Email Addresses"):
2179 cb.append(wx.CheckBox(self, -1, c))
2180 vbs2.Add(cb[-1], 0, wx.EXPAND|wx.ALL, 5)
2181
2182 for c in cb:
2183 c.Enable(False)
2184 cb[0].SetValue(True)
2185
2186 hbs.Add(vbs2, 4, wx.EXPAND|wx.ALL, 5)
2187
2188 return hbs
2189
2191 """Returns each item in the phonebook based on the settings
2192 for all vs selected. The fields are also trimmed to match
2193 what the user requested.
2194
2195 @param includecount: If this is true then the return is
2196 a tuple of (item, number, max) and you can use
2197 number and max to update a progress bar. Note
2198 that some items may be skipped (eg if the user
2199 only wants records with phone numbers and some
2200 records don't have them)
2201 """
2202
2203 data=self._phonebook_module._data
2204 if self.rows_all.GetValue():
2205 rowkeys=data.keys()
2206 else:
2207 rowkeys=self._phonebook_module.GetSelectedRowKeys()
2208 for i,k in enumerate(rowkeys[:]):
2209
2210 if includecount:
2211 yield data[k],i,len(rowkeys)
2212 else:
2213 yield data[k]
2214
2216
2217 dialects=vcard.profiles.keys()
2218 dialects.sort()
2219 default_dialect='vcard2'
2220
2222 BaseExportDialog.__init__(self, parent, title)
2223
2224
2225 vbs=wx.BoxSizer(wx.VERTICAL)
2226
2227 bs=wx.BoxSizer(wx.HORIZONTAL)
2228
2229 bs.Add(wx.StaticText(self, -1, "File"), 0, wx.ALL|wx.ALIGN_CENTRE, 5)
2230 self.filenamectrl=wx.TextCtrl(self, -1, wx.GetApp().config.Read("vcard/export-file", "bitpim.vcf"))
2231 bs.Add(self.filenamectrl, 1, wx.ALL|wx.EXPAND, 5)
2232 self.browsectrl=wx.Button(self, wx.NewId(), "Browse...")
2233 bs.Add(self.browsectrl, 0, wx.ALL|wx.EXPAND, 5)
2234 wx.EVT_BUTTON(self, self.browsectrl.GetId(), self.OnBrowse)
2235
2236 vbs.Add(bs, 0, wx.EXPAND|wx.ALL, 5)
2237
2238 hbs2=wx.BoxSizer(wx.HORIZONTAL)
2239
2240
2241 hbs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Dialect"), wx.VERTICAL)
2242 self.dialectctrl=wx.ListBox(self, -1, style=wx.LB_SINGLE, choices=[vcard.profiles[d]['description'] for d in self.dialects])
2243 default=wx.GetApp().config.Read("vcard/export-format", self.default_dialect)
2244 if default not in self.dialects: default=self.default_dialect
2245 self.dialectctrl.SetSelection(self.dialects.index(default))
2246 hbs.Add(self.dialectctrl, 1, wx.ALL|wx.EXPAND, 5)
2247
2248 hbs2.Add(hbs, 2, wx.EXPAND|wx.ALL, 10)
2249 hbs2.Add(self.GetSelectionGui(self), 5, wx.EXPAND|wx.ALL, 5)
2250
2251 vbs.Add(hbs2, 0, wx.EXPAND|wx.ALL, 5)
2252
2253 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
2254 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
2255 self.SetSizer(vbs)
2256 self.SetAutoLayout(True)
2257 vbs.Fit(self)
2258
2259 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
2260
2262 with guihelper.WXDialogWrapper(wx.FileDialog(self, defaultFile=self.filenamectrl.GetValue(),
2263 wildcard="vCard files (*.vcf)|*.vcf", style=wx.SAVE|wx.CHANGE_DIR),
2264 True) as (dlg, retcode):
2265 if retcode==wx.ID_OK:
2266 self.filenamectrl.SetValue(os.path.join(dlg.GetDirectory(), dlg.GetFilename()))
2267
2268 - def OnOk(self, _):
2290
2292 __pb_keys=(
2293 ('names', ('title', 'first', 'middle', 'last', 'full', 'nickname')),
2294 ('addresses', ('type', 'company', 'street', 'street2', 'city',
2295 'state', 'postalcode', 'country')),
2296 ('numbers', ('number', 'type', 'speeddial')),
2297 ('emails', ('email', 'type')),
2298 ('urls', ('url', 'type')),
2299 ('categories', ('category',)),
2300 ('ringtones', ('ringtone', 'use')),
2301 ('wallpapers', ('wallpaper', 'use')),
2302 ('memos', ('memo',)),
2303 ('flags', ('secret',))
2304 )
2305
2307 super(ExportCSVDialog, self).__init__(parent, title)
2308
2309 vbs=wx.BoxSizer(wx.VERTICAL)
2310 bs=wx.BoxSizer(wx.HORIZONTAL)
2311 bs.Add(wx.StaticText(self, -1, "File"), 0, wx.ALL|wx.ALIGN_CENTRE, 5)
2312 self.filenamectrl=wx.TextCtrl(self, -1, "bitpim.csv")
2313 bs.Add(self.filenamectrl, 1, wx.ALL|wx.EXPAND, 5)
2314 self.browsectrl=wx.Button(self, wx.NewId(), "Browse...")
2315 bs.Add(self.browsectrl, 0, wx.ALL|wx.EXPAND, 5)
2316 vbs.Add(bs, 0, wx.EXPAND|wx.ALL, 5)
2317
2318 vbs.Add(self.GetSelectionGui(self), 5, wx.EXPAND|wx.ALL, 5)
2319
2320 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
2321 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
2322
2323 wx.EVT_BUTTON(self, self.browsectrl.GetId(), self.OnBrowse)
2324 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
2325
2326 self.SetSizer(vbs)
2327 self.SetAutoLayout(True)
2328 vbs.Fit(self)
2329
2331 with guihelper.WXDialogWrapper(wx.FileDialog(self, defaultFile=self.filenamectrl.GetValue(),
2332 wildcard="CSV files (*.csv)|*.csv", style=wx.SAVE|wx.CHANGE_DIR),
2333 True) as (dlg, retcode):
2334 if retcode==wx.ID_OK:
2335 self.filenamectrl.SetValue(os.path.join(dlg.GetDirectory(), dlg.GetFilename()))
2336 - def OnOk(self, _):
2337
2338 filename=self.filenamectrl.GetValue()
2339
2340 key_count={}
2341 for e in self.__pb_keys:
2342 key_count[e[0]]=0
2343 for record in self.GetPhoneBookItems():
2344 for k in record:
2345 if key_count.has_key(k):
2346 key_count[k]=max(key_count[k], len(record[k]))
2347 with file(filename, 'wt') as f:
2348 l=[]
2349 for e in self.__pb_keys:
2350 if key_count[e[0]]:
2351 ll=[e[0]+'_'+x for x in e[1]]
2352 l+=ll*key_count[e[0]]
2353 f.write(','.join(l)+'\n')
2354 for record in self.GetPhoneBookItems():
2355 ll=[]
2356 for e in self.__pb_keys:
2357 key=e[0]
2358 if key_count[key]:
2359 for i in range(key_count[key]):
2360 try:
2361 entry=record[key][i]
2362 except (KeyError, IndexError):
2363 entry={}
2364 for field in e[1]:
2365 _v=entry.get(field, '')
2366 if isinstance(_v, unicode):
2367 _v=_v.encode('ascii', 'ignore')
2368 ll.append('"'+str(_v).replace('"', '')+'"')
2369 f.write(','.join(ll)+'\n')
2370 f.flush()
2371 self.EndModal(wx.ID_OK)
2372
2374
2375 ID_REFRESH=wx.NewId()
2376 ID_CHANGE=wx.NewId()
2377
2378 _categorymessage="eGroupware doesn't create categories correctly via XML-RPC, so you should manually create them via the web interface. " \
2379 "Click to see which ones should be manually created."
2380
2381
2382 - def __init__(self, parent, title, module):
2383 BaseExportDialog.__init__(self, parent, title)
2384
2385 self.module=module
2386 self.parent=parent
2387
2388
2389
2390 vbs=wx.BoxSizer(wx.VERTICAL)
2391
2392 hbs=wx.BoxSizer(wx.HORIZONTAL)
2393 hbs.Add(wx.StaticText(self, -1, "URL"), 0, wx.ALIGN_CENTRE|wx.ALL,2)
2394 self.curl=wx.StaticText(self, -1)
2395 hbs.Add(self.curl, 3, wx.ALIGN_CENTRE_VERTICAL|wx.ALL, 2)
2396 hbs.Add(wx.StaticText(self, -1, "Domain"), 0, wx.ALIGN_CENTRE|wx.ALL,5)
2397 self.cdomain=wx.StaticText(self, -1)
2398 hbs.Add(self.cdomain, 1, wx.ALIGN_CENTRE_VERTICAL|wx.ALL, 2)
2399 hbs.Add(wx.StaticText(self, -1, "User"), 0, wx.ALIGN_CENTRE|wx.ALL,5)
2400 self.cuser=wx.StaticText(self, -1)
2401 hbs.Add(self.cuser, 1, wx.ALIGN_CENTRE_VERTICAL|wx.ALL, 5)
2402 self.cchange=wx.Button(self, self.ID_CHANGE, "Change ...")
2403 hbs.Add(self.cchange, 0, wx.ALL, 2)
2404 vbs.Add(hbs,0,wx.ALL,5)
2405 wx.EVT_BUTTON(self, self.ID_CHANGE, self.OnChangeCreds)
2406 self.sp=None
2407 self.setcreds()
2408
2409 vbs.Add(self.GetSelectionGui(self), 0, wx.EXPAND|wx.ALL, 5)
2410
2411
2412
2413
2414 bs2=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Category Checker"), wx.HORIZONTAL)
2415 bs2.Add(wx.Button(self, self.ID_REFRESH, "Check"), 0, wx.ALL, 5)
2416 self.categoryinfo=wx.TextCtrl(self, -1, self._categorymessage, style=wx.TE_MULTILINE|wx.TE_READONLY)
2417 bs2.Add(self.categoryinfo, 1, wx.EXPAND|wx.ALL, 2)
2418
2419 vbs.Add(bs2, 0, wx.EXPAND|wx.ALL, 5)
2420
2421 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
2422
2423 bs2=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Export Progress"), wx.VERTICAL)
2424 self.progress=wx.Gauge(self, -1, style=wx.GA_HORIZONTAL|wx.GA_SMOOTH)
2425 self.progress_text=wx.StaticText(self, -1, "")
2426 bs2.Add(self.progress, 0, wx.EXPAND|wx.ALL, 5)
2427 bs2.Add(self.progress_text, 0, wx.EXPAND|wx.ALL, 5)
2428 vbs.Add(bs2, 0, wx.EXPAND|wx.ALL, 5)
2429
2430 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
2431 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5)
2432
2433 self.SetSizer(vbs)
2434 self.SetAutoLayout(True)
2435
2436
2437
2438
2439 vbs.Fit(self)
2440
2441 wx.EVT_BUTTON(self, self.ID_REFRESH, self.CategoryCheck)
2442 wx.EVT_BUTTON(self, wx.ID_OK, self.OnOk)
2443
2450
2452 cfg=wx.GetApp().config
2453 self.curl.SetLabel(cfg.Read("egroupware/url", "http://server.example.com/egroupware"))
2454 self.cdomain.SetLabel(cfg.Read("egroupware/domain", "default"))
2455 try:
2456 import getpass
2457 defuser=getpass.getuser()
2458 except:
2459 defuser="user"
2460 self.cuser.SetLabel(cfg.Read("egroupware/user", defuser))
2461
2463 if evt is not None:
2464 evt.Skip()
2465 if self.sp is None:
2466 self.sp=eGroupwareLoginDialog(self, self.module).GetSession(auto=True)
2467 self.setcreds()
2468 self.FindWindowById(wx.ID_OK).Enable(self.sp is not None)
2469 self.FindWindowById(self.ID_REFRESH).Enable(self.sp is not None)
2470
2471
2472 cats=[]
2473 for e in self.GetPhoneBookItems():
2474 for c in e.get("categories", []):
2475 cc=c["category"]
2476 if cc not in cats:
2477 cats.append(cc)
2478
2479 cats.sort()
2480 egcats=[v['name'] for v in self.sp.getcategories()]
2481 nocats=[c for c in cats if c not in egcats]
2482
2483 if len(nocats):
2484 self.categoryinfo.SetValue("eGroupware doesn't have the following categories, which you should manually add:\n\n"+", ".join(nocats))
2485 else:
2486 self.categoryinfo.SetValue("eGroupware has all the categories you use")
2487
2488 - def OnOk(self, _):
2489 if self.sp is None:
2490 self.sp=eGroupwareLoginDialog(self, self.module).GetSession(auto=True)
2491 self.setcreds()
2492 if self.sp is None:
2493 return
2494
2495
2496 doesntexistaction=None
2497 catsmodified=True
2498
2499 setmax=-1
2500 for record,pos,max in self.GetPhoneBookItems(includecount=True):
2501 if catsmodified:
2502
2503 cats=dict( [(v['name'], v['id']) for v in self.sp.getcategories()] )
2504 if max!=setmax:
2505 setmax=max
2506 self.progress.SetRange(max)
2507 self.progress.SetValue(pos)
2508 self.progress_text.SetLabel(nameparser.formatsimplename(record.get("names", [{}])[0]))
2509 wx.SafeYield()
2510 catsmodified,rec=self.FormatRecord(record, cats)
2511 if rec['id']!=0:
2512
2513
2514 if not self.sp.doescontactexist(rec['id']):
2515 if doesntexistaction is None:
2516 with guihelper.WXDialogWrapper(eGroupwareEntryGoneDlg(self, rec['fn']),
2517 True) as (dlg, _):
2518 action=dlg.GetAction()
2519 if dlg.ForAll():
2520 doesntexistaction=action
2521 else: action=doesntexistaction
2522 if action==self._ACTION_RECREATE:
2523 rec['id']=0
2524 elif action==self._ACTION_IGNORE:
2525 continue
2526 elif action==self._ACTION_DELETE:
2527 found=False
2528 for serial in record["serials"]:
2529 if serial["sourcetype"]=="bitpim":
2530 self.parent.GetActivePhonebookWidget().DeleteBySerial(serial)
2531 found=True
2532 break
2533 assert found
2534 continue
2535
2536 newid=self.sp.writecontact(rec)
2537 found=False
2538 for serial in record["serials"]:
2539 if serial["sourcetype"]=="bitpim":
2540 self.parent.GetActivePhonebookWidget().UpdateSerial(serial, {"sourcetype": "egroupware", "id": newid})
2541 found=True
2542 break
2543 assert found
2544
2545
2546 self.EndModal(wx.ID_OK)
2547
2548
2549
2644
2645 _ACTION_RECREATE=1
2646 _ACTION_IGNORE=2
2647 _ACTION_DELETE=3
2648
2649 -class eGroupwareEntryGoneDlg(wx.Dialog):
2650
2651 choices=( ("Re-create entry on server", ExporteGroupwareDialog._ACTION_RECREATE),
2652 ("Delete the entry in BitPim", ExporteGroupwareDialog._ACTION_DELETE),
2653 ("Ignore this entry for now", ExporteGroupwareDialog._ACTION_IGNORE)
2654 )
2655
2656 - def __init__(self, parent, details):
2657 wx.Dialog.__init__(self, parent, -1, title="Entry deleted on server")
2658 vbs=wx.BoxSizer(wx.VERTICAL)
2659 vbs.Add(wx.StaticText(self, -1, "The entry for \"%s\" has\nbeen deleted on the server." % (details,) ), 0, wx.EXPAND|wx.ALL, 5)
2660 self.rb=wx.RadioBox(self, -1, "Action to take", choices=[t for t,a in self.choices])
2661 vbs.Add(self.rb, 0, wx.EXPAND|wx.ALL, 5)
2662 self.always=wx.CheckBox(self, -1, "Always take this action")
2663 vbs.Add(self.always, 0, wx.ALL|wx.ALIGN_CENTRE, 5)
2664
2665 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL,5)
2666 vbs.Add(self.CreateButtonSizer(wx.OK|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
2667 self.SetSizer(vbs)
2668 vbs.Fit(self)
2669
2670 - def GetAction(self):
2671 return self.choices[self.rb.GetSelection()][1]
2672
2674 return self.always.GetValue()
2675
2680
2686
2691
2697
2702
2708
2710 import call_history_export
2711 with guihelper.WXDialogWrapper(call_history_export.ExportCallHistoryDialog(parent, 'Export Call History'),
2712 True):
2713 pass
2714
2719
2724