Update locales.
[wammu.git] / Wammu / Editor.py
blob74d794853261514509614d1bfc716b0a90706b26
1 # -*- coding: UTF-8 -*-
2 # vim: expandtab sw=4 ts=4 sts=4:
3 '''
4 Wammu - Phone manager
5 Item editors
6 '''
7 __author__ = 'Michal Čihař'
8 __email__ = 'michal@cihar.com'
9 __license__ = '''
10 Copyright © 2003 - 2008 Michal Čihař
12 This program is free software; you can redistribute it and/or modify it
13 under the terms of the GNU General Public License version 2 as published by
14 the Free Software Foundation.
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
19 more details.
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 '''
26 import wx
27 from wx import DateTimeFromDMY, DateTime_Today
28 import wx.calendar
29 import wx.lib.masked.timectrl
30 from wx.lib.masked import Ctrl as maskedCtrl
31 from Wammu.Paths import *
32 import sys
33 import datetime
34 import time
35 import Wammu
36 import Wammu.Data
37 import Wammu.Utils
38 import Wammu.Select
39 import Wammu.PhoneValidator
40 from Wammu.Locales import StrConv, UnicodeConv
43 def TextToTime(txt, config):
44 hms = txt.split(':')
45 try:
46 return datetime.time(int(hms[0]), int(hms[1]), int(hms[2]))
47 except UnicodeEncodeError:
48 hms = config.Read('/Wammu/DefaultTime').split(':')
49 return datetime.time(int(hms[0]), int(hms[1]), int(hms[2]))
51 def TextToDate(txt):
52 dmy = txt.split('.')
53 try:
54 return datetime.date(int(dmy[2]), int(dmy[1]), int(dmy[0]))
55 except UnicodeEncodeError:
56 return datetime.date.today()
58 def TimeToText(time, config):
59 try:
60 try:
61 time = time.time()
62 except:
63 pass
64 return time.isoformat()
65 except:
66 return config.Read('/Wammu/DefaultTime')
68 def DateToText(date, config):
69 try:
70 try:
71 date = date.date()
72 except:
73 pass
74 return date.strftime('%d.%m.%Y')
75 except:
76 return datetime.datetime.fromtimestamp(time.time() + 24*60*60*config.ReadInt('/Wammu/DefaultDateOffset')).date().strftime('%d.%m.%Y')
78 class TimeCtrl(wx.lib.masked.timectrl.TimeCtrl):
79 def Validate(self):
80 return self.IsValid(self.GetValue())
82 class CalendarPopup(wx.PopupTransientWindow):
83 def __init__(self, parent):
84 wx.PopupTransientWindow.__init__(self, parent, wx.SIMPLE_BORDER)
85 self.cal = wx.calendar.CalendarCtrl(self, -1, pos = (0, 0), style = wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
86 sz = self.cal.GetBestSize()
87 self.SetSize(sz)
89 class DateControl(wx.Panel):
90 def __init__(self, parent, value):
91 wx.Panel.__init__(self, parent, -1)
93 self.sizer = wx.FlexGridSizer(1, 2)
94 self.sizer.AddGrowableCol(0)
95 self.textCtrl = maskedCtrl(self, -1, autoformat = 'EUDATEDDMMYYYY.', validRequired=True, emptyInvalid=True)
96 Wammu.Utils.FixupMaskedEdit(self.textCtrl)
97 self.textCtrl.SetValue(value)
98 self.bCtrl = wx.BitmapButton(self, -1, wx.Bitmap(MiscPath('downarrow')))
99 self.sizer.AddMany([
100 (self.textCtrl, 1, wx.EXPAND),
101 (self.bCtrl, 1, wx.EXPAND),
103 self.sizer.Fit(self)
104 self.SetAutoLayout(True)
105 self.SetSizer(self.sizer)
106 wx.EVT_BUTTON(self.bCtrl,self.bCtrl.GetId(),self.OnButton)
107 wx.EVT_SET_FOCUS(self,self.OnFocus)
109 def GetValidator(self):
110 return self.textCtrl.GetValidator()
112 def Validate(self):
113 return self.textCtrl.Validate()
115 def OnFocus(self,evt):
116 self.textCtrl.SetFocus()
117 evt.Skip()
119 def OnButton(self,evt):
120 self.pop = CalendarPopup(self)
121 txtValue = self.GetValue()
122 dmy = txtValue.split('.')
123 didSet = False
124 if len(dmy) == 3:
125 d = int(dmy[0])
126 m = int(dmy[1]) - 1
127 y = int(dmy[2])
128 if d > 0 and d < 31:
129 if m >= 0 and m < 12:
130 if y > 1000:
131 self.pop.cal.SetDate(DateTimeFromDMY(d,m,y))
132 didSet = True
133 if not didSet:
134 self.pop.cal.SetDate(DateTime_Today())
136 pos = self.ClientToScreen( (0,0) )
137 display_size = wx.GetDisplaySize()
138 popup_size = self.pop.GetSize()
139 control_size = self.GetSize()
141 pos.x -= (popup_size.x - control_size.x) / 2
142 if pos.x + popup_size.x > display_size.x:
143 pos.x = display_size.x - popup_size.x
144 if pos.x < 0:
145 pos.x = 0
147 pos.y += control_size.height
148 if pos.y + popup_size.y > display_size.y:
149 pos.y = display_size.y - popup_size.y
150 if pos.y < 0:
151 pos.y = 0
152 self.pop.MoveXY(pos.x,pos.y)
153 wx.calendar.EVT_CALENDAR_DAY(self, self.pop.cal.GetId(),self.OnCalSelected)
154 self.pop.Popup()
156 def Enable(self, flag):
157 wx.PyControl.Enable(self, flag)
158 self.textCtrl.Enable(flag)
159 self.bCtrl.Enable(flag)
161 def SetValue(self,value):
162 self.textCtrl.SetValue(value)
164 def GetValue(self):
165 return self.textCtrl.GetValue()
167 def OnCalSelected(self,evt):
168 date = self.pop.cal.GetDate()
169 self.SetValue('%02d.%02d.%04d' % (
170 date.GetDay(),
171 date.GetMonth() + 1,
172 date.GetYear()))
173 self.pop.Dismiss()
174 evt.Skip()
176 class ContactEdit(wx.Panel):
178 Contact editor
180 def __init__(self, parent, val, values):
181 wx.Panel.__init__(self, parent, -1)
182 self.values = values
183 self.sizer = wx.FlexGridSizer(1, 3, 2, 2)
184 self.sizer.AddGrowableCol(1)
185 self.edit = wx.SpinCtrl(self, -1, str(val), style = wx.SP_WRAP|wx.SP_ARROW_KEYS, min = 0, max = 10000, initial = val, size = (200, -1))
186 self.txt = wx.StaticText(self, -1, self.GetText(val))
187 self.btn = wx.Button(self, -1, '...', style = wx.BU_EXACTFIT)
188 self.sizer.AddMany([
189 (self.edit, 0, wx.EXPAND),
190 (self.txt, 1, wx.EXPAND),
191 (self.btn, 0, wx.EXPAND),
193 wx.EVT_TEXT(self.edit, self.edit.GetId(), self.OnChange)
194 wx.EVT_BUTTON(self.btn, self.btn.GetId(), self.OnContacts)
195 self.sizer.Fit(self)
196 self.SetAutoLayout(True)
197 self.SetSizer(self.sizer)
199 def OnChange(self, evt):
200 self.txt.SetLabel(self.GetText(self.edit.GetValue()))
201 self.sizer.Fit(self)
202 # self.sizer.SetSizeHints(self)
204 def OnContacts(self, evt):
205 i = Wammu.Select.SelectContact(self, self.values)
206 if i != -1:
207 self.SetValue(i)
209 def GetText(self, val):
210 if val < 1:
211 return _('None')
212 else:
213 l = Wammu.Utils.SearchLocation(self.values, val)
214 if l == -1:
215 return _('Unknown')
216 else:
217 return self.values[l]['Name']
219 def GetValue(self):
220 return self.edit.GetValue()
222 def SetValue(self, value):
223 return self.edit.SetValue(value)
226 class GenericEditor(wx.Dialog):
228 Generic editor customised further by it's descendants
230 def __init__(self, parent, cfg, values, entry, internalname, name, location, type, typename, typevalues, itemtypes ):
231 if entry == {}:
232 title = _('Creating new %s') % name
233 self.wasempty = True
234 else:
235 title = _('Editing %(name)s %(location)s') % {'name':name, 'location':location}
236 self.wasempty = False
238 wx.Dialog.__init__(self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
239 self.rows = 0
240 self.entry = entry
241 self.values = values
242 self.type = type
243 self.cfg = cfg
244 self.internalname = internalname
245 self.itemtypes = itemtypes
246 self.sizer = wx.GridBagSizer(5, 5)
247 self.sizer.AddGrowableCol(2)
248 self.sizer.AddGrowableCol(5)
249 if self.wasempty:
250 entry['Location'] = 0
251 entry[type] = self.cfg.Read('/Defaults/Type-%s-%s' % (internalname, type))
253 self.sizer.Add(wx.StaticText(self, -1, _('Location (0 = auto):')), (0, 0), (1, 4))
254 # there used to be sys.maxint on following line, but it's too large on amd64 (or there is bug in wxPython)
255 self.locationedit = wx.SpinCtrl(self, -1, str(entry['Location']), style = wx.SP_WRAP|wx.SP_ARROW_KEYS , min = 0, max = 2147483647, initial = entry['Location'])
256 self.sizer.Add(self.locationedit, (0, 4), (1, 4))
258 self.sizer.Add(wx.StaticText(self, -1, typename), (1, 0), (1, 4))
259 self.typeedit = wx.ComboBox(self, -1, entry[type], choices = typevalues, style = wx.CB_READONLY)
260 self.sizer.Add(self.typeedit, (1, 4), (1, 4))
262 self.rowoffset = 2
264 self.Bind(wx.EVT_TEXT, self.OnTypeChange, self.typeedit)
266 self.edits = {}
267 self.types = {}
268 self.fulltypes = {}
269 x = 0
270 if self.wasempty:
271 for x in range(self.cfg.ReadInt('/Wammu/DefaultEntries')):
272 entrytype = self.cfg.Read('/Defaults/Entry-%s-%d' % (self.internalname, x))
273 if entrytype != '':
274 self.AddEdit(x, {'Type': entrytype, 'Value': '', 'VoiceTag': 0, 'AddError': 0})
275 else:
276 self.AddEdit(x)
277 else:
278 for i in range(len(entry['Entries'])):
279 self.AddEdit(i, entry['Entries'][i])
281 self.more = wx.Button(self, wx.ID_ADD)
282 self.more.SetToolTipString(_('Add one more field.'))
283 self.button_sizer = wx.StdDialogButtonSizer()
284 self.button_sizer.AddButton(wx.Button(self, wx.ID_OK))
285 self.button_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))
286 self.button_sizer.SetNegativeButton(self.more)
287 self.button_sizer.Realize()
288 self.Bind(wx.EVT_BUTTON, self.Okay, id = wx.ID_OK)
289 self.Bind(wx.EVT_BUTTON, self.More, self.more)
291 self.SetAutoLayout(True)
292 self.SetSizer(self.sizer)
294 self.AddButtons()
296 def AddButtons(self):
297 row = self.rowoffset + self.rows + 1
298 self.sizer.Add(self.button_sizer, pos = (row, 1), span = wx.GBSpan(colspan = 7), flag = wx.ALIGN_RIGHT)
299 self.sizer.Fit(self)
300 self.sizer.SetSizeHints(self)
301 self.sizer.Layout()
303 def RemoveButtons(self):
304 self.sizer.Detach(self.button_sizer)
306 def AddEdit(self, row, value = {'Type':'', 'Value':''}):
307 self.rows += 1
308 self.sizer.Add(wx.StaticText(self, -1, '%d.' % (row + 1), size = (20, -1)), (row + self.rowoffset, 0))
309 combo = wx.ComboBox(self, -1, value['Type'], choices = self.itemtypes + [''], style = wx.CB_READONLY, size = (180, -1))
310 combo.row = row
311 self.sizer.Add(combo, (row + self.rowoffset, 1), (1, 3))
312 self.Bind(wx.EVT_TEXT, self.OnItemTypeChange, combo)
313 self.AddTypeEdit(row, value)
315 def AddTypeEdit(self, row, value):
316 type = Wammu.Utils.GetItemType(value['Type'])
317 self.fulltypes[row] = value['Type']
318 self.types[row] = type
319 if type == 'text' or type == None:
320 # text editor
321 edit = wx.TextCtrl(self, -1, StrConv(value['Value']), size = (200, -1))
322 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
323 self.edits[row] = [edit]
324 elif type == 'phone':
325 # phone editor with voice tag
326 edit = wx.TextCtrl(self, -1, StrConv(value['Value']), size = (150, -1), validator = Wammu.PhoneValidator.PhoneValidator(pause = True))
327 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 3))
328 try:
329 v = hex(value['VoiceTag'])
330 except:
331 v = '0x0'
332 if v[-1] == 'L':
333 v = v[:-1]
334 edit2 = wx.TextCtrl(self, -1, v, size = (50, -1))
335 self.sizer.Add(edit2, (row + self.rowoffset, 7), (1, 1))
336 self.edits[row] = [edit, edit2]
337 elif type == 'bool':
338 # boolean
339 try:
340 val = bool(value['Value'])
341 except:
342 val = False
343 edit = wx.CheckBox(self, -1, '', size = (200, -1))
344 edit.SetValue(val)
345 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
346 self.edits[row] = [edit]
347 elif type == 'contact':
348 # contact editor
349 try:
350 val = int(value['Value'])
351 except:
352 val = 0
353 edit = wx.SpinCtrl(self, -1, str(val), style = wx.SP_WRAP|wx.SP_ARROW_KEYS, min = 0, max = 10000, initial = val, size = (50, -1))
354 edit.row = row
355 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 1))
356 edit2 = wx.Button(self, -1, self.GetContactText(val), style = wx.BU_EXACTFIT, size = (150, -1))
357 edit2.row = row
358 self.sizer.Add(edit2, (row + self.rowoffset, 5), (1, 3))
359 self.edits[row] = [edit, edit2]
360 self.Bind(wx.EVT_SPINCTRL, self.OnContactSpinChange, edit)
361 self.Bind(wx.EVT_BUTTON, self.OnContactButton, edit2)
362 elif type == 'id':
363 # ID editor
364 try:
365 v = hex(value['Value'])
366 except:
367 v = '0x0'
368 if v[-1] == 'L':
369 v = v[:-1]
370 edit = wx.TextCtrl(self, -1, StrConv(v), size = (200, -1))
371 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
372 self.edits[row] = [edit]
373 elif type == 'category' or type == 'number':
374 # number editor
375 # FIXME: category should be selectable
376 try:
377 val = int(value['Value'])
378 except:
379 val = 0
380 edit = wx.SpinCtrl(self, -1, str(val), style = wx.SP_WRAP|wx.SP_ARROW_KEYS, min = -10000, max = 10000, initial = val, size = (200, -1))
381 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
382 self.edits[row] = [edit]
383 elif type == 'datetime':
384 # date + time editor
385 edit = TimeCtrl( self, -1, fmt24hr=True)
386 Wammu.Utils.FixupMaskedEdit(edit)
387 edit.SetValue(TimeToText(value['Value'], self.cfg))
388 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 2))
389 edit2 = DateControl(self, DateToText(value['Value'], self.cfg))
390 self.sizer.Add(edit2, (row + self.rowoffset, 6), (1, 2))
391 self.edits[row] = [edit, edit2]
392 elif type == 'date':
393 # date editor
394 edit = DateControl(self, DateToText(value['Value'], self.cfg))
395 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
396 self.edits[row] = [edit]
397 else:
398 print 'warning: creating TextCtrl for %s' % type
399 edit = wx.TextCtrl(self, -1, StrConv(value['Value']), size = (200, -1))
400 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
401 self.edits[row] = [edit]
402 self.sizer.Fit(self)
403 self.sizer.SetSizeHints(self)
404 self.sizer.Layout()
406 def OnContactSpinChange(self, evt):
407 row = evt.GetEventObject().row
408 self.edits[row][1].SetLabel(self.GetContactText(evt.GetInt()))
410 def OnContactButton(self, evt):
411 row = evt.GetEventObject().row
412 val = Wammu.Select.SelectContact(self, [] + self.values['contact']['ME'])
413 if val != -1:
414 self.edits[row][0].SetValue(val)
415 self.edits[row][1].SetLabel(self.GetContactText(val))
417 def GetContactText(self, val):
418 if val < 1:
419 return _('None')
420 else:
421 l = Wammu.Utils.SearchLocation(self.values['contact']['ME'], val)
422 if l == -1:
423 return _('Unknown')
424 else:
425 return self.values['contact']['ME'][l]['Name']
427 def DelTypeEdit(self, row):
428 for x in self.edits[row]:
429 if x is not None:
430 self.sizer.Detach(x)
431 x.Destroy()
432 self.edits[row] = [None]
434 def GetTypeEditValue(self, row):
435 if self.types[row] == 'date':
436 return TextToDate(self.edits[row][0].GetValue())
437 elif self.types[row] == 'datetime':
438 return datetime.datetime.combine(TextToDate(self.edits[row][1].GetValue()), TextToTime(self.edits[row][0].GetValue(), self.cfg))
439 elif self.types[row] == 'id':
440 return int(self.edits[row][0].GetValue(), 16)
441 elif self.types[row] in ['contact', 'bool', 'category', 'number']:
442 return int(self.edits[row][0].GetValue())
443 elif self.types[row] in ['phone', 'text']:
444 return UnicodeConv(self.edits[row][0].GetValue())
445 else:
446 return self.edits[row][0].GetValue()
448 def GetTypeEditVoiceTag(self, row):
449 if self.types[row] == 'phone':
450 return int(self.edits[row][1].GetValue(), 16)
451 return 0
453 def OnItemTypeChange(self, evt):
454 row = evt.GetEventObject().row
455 type = evt.GetString()
456 val = self.GetTypeEditValue(row)
457 self.DelTypeEdit(row)
458 self.AddTypeEdit(row, {'Type': type, 'Value':val})
460 def OnTypeChange(self, evt):
461 self.locationedit.SetValue(0)
463 def More(self, evt):
464 self.RemoveButtons()
465 self.AddEdit(self.rows)
466 self.AddButtons()
468 def Okay(self, evt):
469 if not self.Validate():
470 return
472 v = []
473 for row in range(self.rows):
474 t = self.fulltypes[row]
475 if t != '':
476 v.append({'Type' : t, 'Value' : self.GetTypeEditValue(row), 'VoiceTag' : self.GetTypeEditVoiceTag(row)})
478 self.entry['Entries'] = v
479 self.entry[self.type] = self.typeedit.GetValue()
480 self.entry['Location'] = self.locationedit.GetValue()
482 # Remember default type
483 if self.wasempty:
484 self.cfg.Write('/Defaults/Type-%s-%s' % (self.internalname, self.type),
485 self.entry[self.type])
487 self.EndModal(wx.ID_OK)
489 class ContactEditor(GenericEditor):
490 def __init__(self, parent, cfg, values, entry):
491 if entry == {}:
492 location = ''
493 else:
494 location = '%s:%d' % (entry['MemoryType'], entry['Location'])
495 GenericEditor.__init__(self, parent, cfg, values, entry, 'contact', _('contact'), location, 'MemoryType', _('Memory type'), Wammu.Data.ContactMemoryTypes, Wammu.Data.MemoryValueTypes)
497 class CalendarEditor(GenericEditor):
498 def __init__(self, parent, cfg, values, entry):
499 if entry == {}:
500 location = ''
501 else:
502 location = '%d' % entry['Location']
503 GenericEditor.__init__(self, parent, cfg, values, entry, 'calendar', _('calendar event'), location, 'Type', _('Event type'), Wammu.Data.CalendarTypes, Wammu.Data.CalendarValueTypes)
505 class TodoEditor(GenericEditor):
506 def __init__(self, parent, cfg, values, entry):
507 if entry == {}:
508 location = ''
509 else:
510 location = '%d' % entry['Location']
511 GenericEditor.__init__(self, parent, cfg, values, entry, 'todo', _('todo item'), location, 'Priority', _('Priority'), Wammu.Data.TodoPriorities, Wammu.Data.TodoValueTypes)
513 def Okay(self, evt):
514 self.entry['Type'] = 'MEMO'
515 GenericEditor.Okay(self, evt)