Translated using Weblate.
[wammu.git] / Wammu / Editor.py
bloba22a2e4356525aca5093015daab5edb9c18c2a33
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 - 2010 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 datetime
33 import time
34 import Wammu
35 import Wammu.Data
36 import Wammu.Utils
37 import Wammu.Select
38 import Wammu.PhoneValidator
39 from Wammu.Locales import StrConv, UnicodeConv
42 def TextToTime(txt, config):
43 hms = txt.split(':')
44 try:
45 return datetime.time(int(hms[0]), int(hms[1]), int(hms[2]))
46 except UnicodeEncodeError:
47 hms = config.Read('/Wammu/DefaultTime').split(':')
48 return datetime.time(int(hms[0]), int(hms[1]), int(hms[2]))
50 def TextToDate(txt):
51 dmy = txt.split('.')
52 try:
53 return datetime.date(int(dmy[2]), int(dmy[1]), int(dmy[0]))
54 except UnicodeEncodeError:
55 return datetime.date.today()
57 def TimeToText(time, config):
58 try:
59 try:
60 time = time.time()
61 except:
62 pass
63 return time.isoformat()
64 except:
65 return config.Read('/Wammu/DefaultTime')
67 def DateToText(date, config):
68 try:
69 try:
70 date = date.date()
71 except:
72 pass
73 return date.strftime('%d.%m.%Y')
74 except:
75 return datetime.datetime.fromtimestamp(time.time() + 24*60*60*config.ReadInt('/Wammu/DefaultDateOffset')).date().strftime('%d.%m.%Y')
77 class TimeCtrl(wx.lib.masked.timectrl.TimeCtrl):
78 def Validate(self):
79 return self.IsValid(self.GetValue())
81 class CalendarPopup(wx.PopupTransientWindow):
82 def __init__(self, parent):
83 wx.PopupTransientWindow.__init__(self, parent, wx.SIMPLE_BORDER)
84 self.cal = wx.calendar.CalendarCtrl(self, -1, pos = (0, 0), style = wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
85 sz = self.cal.GetBestSize()
86 self.SetSize(sz)
88 class DateControl(wx.Panel):
89 def __init__(self, parent, value):
90 wx.Panel.__init__(self, parent, -1)
92 self.sizer = wx.FlexGridSizer(1, 2)
93 self.sizer.AddGrowableCol(0)
94 self.textCtrl = maskedCtrl(self, -1, autoformat = 'EUDATEDDMMYYYY.', validRequired=True, emptyInvalid=True)
95 Wammu.Utils.FixupMaskedEdit(self.textCtrl)
96 self.textCtrl.SetValue(value)
97 self.bCtrl = wx.BitmapButton(self, -1, wx.Bitmap(MiscPath('downarrow')))
98 self.sizer.AddMany([
99 (self.textCtrl, 1, wx.EXPAND),
100 (self.bCtrl, 1, wx.EXPAND),
102 self.sizer.Fit(self)
103 self.SetAutoLayout(True)
104 self.SetSizer(self.sizer)
105 wx.EVT_BUTTON(self.bCtrl,self.bCtrl.GetId(),self.OnButton)
106 wx.EVT_SET_FOCUS(self,self.OnFocus)
108 def GetValidator(self):
109 return self.textCtrl.GetValidator()
111 def Validate(self):
112 return self.textCtrl.Validate()
114 def OnFocus(self,evt):
115 self.textCtrl.SetFocus()
116 evt.Skip()
118 def OnButton(self,evt):
119 self.pop = CalendarPopup(self)
120 txtValue = self.GetValue()
121 dmy = txtValue.split('.')
122 didSet = False
123 if len(dmy) == 3:
124 d = int(dmy[0])
125 m = int(dmy[1]) - 1
126 y = int(dmy[2])
127 if d > 0 and d < 31:
128 if m >= 0 and m < 12:
129 if y > 1000:
130 self.pop.cal.SetDate(DateTimeFromDMY(d,m,y))
131 didSet = True
132 if not didSet:
133 self.pop.cal.SetDate(DateTime_Today())
135 pos = self.ClientToScreen( (0,0) )
136 display_size = wx.GetDisplaySize()
137 popup_size = self.pop.GetSize()
138 control_size = self.GetSize()
140 pos.x -= (popup_size.x - control_size.x) / 2
141 if pos.x + popup_size.x > display_size.x:
142 pos.x = display_size.x - popup_size.x
143 if pos.x < 0:
144 pos.x = 0
146 pos.y += control_size.height
147 if pos.y + popup_size.y > display_size.y:
148 pos.y = display_size.y - popup_size.y
149 if pos.y < 0:
150 pos.y = 0
151 self.pop.MoveXY(pos.x,pos.y)
152 wx.calendar.EVT_CALENDAR_DAY(self, self.pop.cal.GetId(),self.OnCalSelected)
153 self.pop.Popup()
155 def Enable(self, flag):
156 wx.PyControl.Enable(self, flag)
157 self.textCtrl.Enable(flag)
158 self.bCtrl.Enable(flag)
160 def SetValue(self,value):
161 self.textCtrl.SetValue(value)
163 def GetValue(self):
164 return self.textCtrl.GetValue()
166 def OnCalSelected(self,evt):
167 date = self.pop.cal.GetDate()
168 self.SetValue('%02d.%02d.%04d' % (
169 date.GetDay(),
170 date.GetMonth() + 1,
171 date.GetYear()))
172 self.pop.Dismiss()
173 evt.Skip()
175 class ContactEdit(wx.Panel):
177 Contact editor
179 def __init__(self, parent, val, values):
180 wx.Panel.__init__(self, parent, -1)
181 self.values = values
182 self.sizer = wx.FlexGridSizer(1, 3, 2, 2)
183 self.sizer.AddGrowableCol(1)
184 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))
185 self.txt = wx.StaticText(self, -1, self.GetText(val))
186 self.btn = wx.Button(self, -1, '...', style = wx.BU_EXACTFIT)
187 self.sizer.AddMany([
188 (self.edit, 0, wx.EXPAND),
189 (self.txt, 1, wx.EXPAND),
190 (self.btn, 0, wx.EXPAND),
192 wx.EVT_TEXT(self.edit, self.edit.GetId(), self.OnChange)
193 wx.EVT_BUTTON(self.btn, self.btn.GetId(), self.OnContacts)
194 self.sizer.Fit(self)
195 self.SetAutoLayout(True)
196 self.SetSizer(self.sizer)
198 def OnChange(self, evt):
199 self.txt.SetLabel(self.GetText(self.edit.GetValue()))
200 self.sizer.Fit(self)
201 # self.sizer.SetSizeHints(self)
203 def OnContacts(self, evt):
204 i = Wammu.Select.SelectContact(self, self.values)
205 if i != -1:
206 self.SetValue(i)
208 def GetText(self, val):
209 if val < 1:
210 return _('None')
211 else:
212 l = Wammu.Utils.SearchLocation(self.values, val)
213 if l == -1:
214 return _('Unknown')
215 else:
216 return self.values[l]['Name']
218 def GetValue(self):
219 return self.edit.GetValue()
221 def SetValue(self, value):
222 return self.edit.SetValue(value)
225 class GenericEditor(wx.Dialog):
227 Generic editor customised further by it's descendants
229 def __init__(self, parent, cfg, values, entry, internalname, name, location, type, typename, typevalues, itemtypes ):
230 if entry == {}:
231 title = _('Creating new %s') % name
232 self.wasempty = True
233 else:
234 title = _('Editing %(name)s %(location)s') % {'name':name, 'location':location}
235 self.wasempty = False
237 wx.Dialog.__init__(self, parent, -1, title, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
238 self.rows = 0
239 self.entry = entry
240 self.values = values
241 self.type = type
242 self.cfg = cfg
243 self.internalname = internalname
244 self.itemtypes = itemtypes
245 self.sizer = wx.GridBagSizer(5, 5)
246 self.sizer.AddGrowableCol(2)
247 self.sizer.AddGrowableCol(5)
248 if self.wasempty:
249 entry['Location'] = 0
250 entry[type] = self.cfg.Read('/Defaults/Type-%s-%s' % (internalname, type))
252 self.sizer.Add(wx.StaticText(self, -1, _('Location (0 = auto):')), (0, 0), (1, 4))
253 # there used to be sys.maxint on following line, but it's too large on amd64 (or there is bug in wxPython)
254 self.locationedit = wx.SpinCtrl(self, -1, str(entry['Location']), style = wx.SP_WRAP|wx.SP_ARROW_KEYS , min = 0, max = 2147483647, initial = entry['Location'])
255 self.sizer.Add(self.locationedit, (0, 4), (1, 4))
257 self.sizer.Add(wx.StaticText(self, -1, typename), (1, 0), (1, 4))
258 self.typeedit = wx.ComboBox(self, -1, entry[type], choices = typevalues, style = wx.CB_READONLY)
259 self.sizer.Add(self.typeedit, (1, 4), (1, 4))
261 self.rowoffset = 2
263 self.Bind(wx.EVT_TEXT, self.OnTypeChange, self.typeedit)
265 self.edits = {}
266 self.types = {}
267 self.fulltypes = {}
268 x = 0
269 if self.wasempty:
270 for x in range(self.cfg.ReadInt('/Wammu/DefaultEntries')):
271 entrytype = self.cfg.Read('/Defaults/Entry-%s-%d' % (self.internalname, x))
272 if entrytype != '':
273 self.AddEdit(x, {'Type': entrytype, 'Value': '', 'VoiceTag': 0, 'AddError': 0})
274 else:
275 self.AddEdit(x)
276 else:
277 for i in range(len(entry['Entries'])):
278 self.AddEdit(i, entry['Entries'][i])
280 self.more = wx.Button(self, wx.ID_ADD)
281 self.more.SetToolTipString(_('Add one more field.'))
282 self.button_sizer = wx.StdDialogButtonSizer()
283 self.button_sizer.AddButton(wx.Button(self, wx.ID_OK))
284 self.button_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))
285 self.button_sizer.SetNegativeButton(self.more)
286 self.button_sizer.Realize()
287 self.Bind(wx.EVT_BUTTON, self.Okay, id = wx.ID_OK)
288 self.Bind(wx.EVT_BUTTON, self.More, self.more)
290 self.SetAutoLayout(True)
291 self.SetSizer(self.sizer)
293 self.AddButtons()
295 def AddButtons(self):
296 row = self.rowoffset + self.rows + 1
297 self.sizer.Add(self.button_sizer, pos = (row, 1), span = wx.GBSpan(colspan = 7), flag = wx.ALIGN_RIGHT)
298 self.sizer.Fit(self)
299 self.sizer.SetSizeHints(self)
300 self.sizer.Layout()
302 def RemoveButtons(self):
303 self.sizer.Detach(self.button_sizer)
305 def AddEdit(self, row, value = {'Type':'', 'Value':''}):
306 self.rows += 1
307 self.sizer.Add(wx.StaticText(self, -1, '%d.' % (row + 1), size = (20, -1)), (row + self.rowoffset, 0))
308 combo = wx.ComboBox(self, -1, value['Type'], choices = self.itemtypes + [''], style = wx.CB_READONLY, size = (180, -1))
309 combo.row = row
310 self.sizer.Add(combo, (row + self.rowoffset, 1), (1, 3))
311 self.Bind(wx.EVT_TEXT, self.OnItemTypeChange, combo)
312 self.AddTypeEdit(row, value)
314 def AddTypeEdit(self, row, value):
315 type = Wammu.Utils.GetItemType(value['Type'])
316 self.fulltypes[row] = value['Type']
317 self.types[row] = type
318 if type == 'text' or type == None:
319 # text editor
320 edit = wx.TextCtrl(self, -1, StrConv(value['Value']), size = (200, -1))
321 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
322 self.edits[row] = [edit]
323 elif type == 'phone':
324 # phone editor with voice tag
325 edit = wx.TextCtrl(self, -1, StrConv(value['Value']), size = (150, -1), validator = Wammu.PhoneValidator.PhoneValidator(pause = True))
326 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 3))
327 try:
328 v = hex(value['VoiceTag'])
329 except:
330 v = '0x0'
331 if v[-1] == 'L':
332 v = v[:-1]
333 edit2 = wx.TextCtrl(self, -1, v, size = (50, -1))
334 self.sizer.Add(edit2, (row + self.rowoffset, 7), (1, 1))
335 self.edits[row] = [edit, edit2]
336 elif type == 'bool':
337 # boolean
338 try:
339 val = bool(value['Value'])
340 except:
341 val = False
342 edit = wx.CheckBox(self, -1, '', size = (200, -1))
343 edit.SetValue(val)
344 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
345 self.edits[row] = [edit]
346 elif type == 'contact':
347 # contact editor
348 try:
349 val = int(value['Value'])
350 except:
351 val = 0
352 edit = wx.SpinCtrl(self, -1, str(val), style = wx.SP_WRAP|wx.SP_ARROW_KEYS, min = 0, max = 10000, initial = val, size = (50, -1))
353 edit.row = row
354 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 1))
355 edit2 = wx.Button(self, -1, self.GetContactText(val), style = wx.BU_EXACTFIT, size = (150, -1))
356 edit2.row = row
357 self.sizer.Add(edit2, (row + self.rowoffset, 5), (1, 3))
358 self.edits[row] = [edit, edit2]
359 self.Bind(wx.EVT_SPINCTRL, self.OnContactSpinChange, edit)
360 self.Bind(wx.EVT_BUTTON, self.OnContactButton, edit2)
361 elif type == 'id':
362 # ID editor
363 try:
364 v = hex(value['Value'])
365 except:
366 v = '0x0'
367 if v[-1] == 'L':
368 v = v[:-1]
369 edit = wx.TextCtrl(self, -1, StrConv(v), size = (200, -1))
370 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
371 self.edits[row] = [edit]
372 elif type == 'category' or type == 'number':
373 # number editor
374 # FIXME: category should be selectable
375 try:
376 val = int(value['Value'])
377 except:
378 val = 0
379 edit = wx.SpinCtrl(self, -1, str(val), style = wx.SP_WRAP|wx.SP_ARROW_KEYS, min = -10000, max = 10000, initial = val, size = (200, -1))
380 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
381 self.edits[row] = [edit]
382 elif type == 'datetime':
383 # date + time editor
384 edit = TimeCtrl( self, -1, fmt24hr=True)
385 Wammu.Utils.FixupMaskedEdit(edit)
386 edit.SetValue(TimeToText(value['Value'], self.cfg))
387 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 2))
388 edit2 = DateControl(self, DateToText(value['Value'], self.cfg))
389 self.sizer.Add(edit2, (row + self.rowoffset, 6), (1, 2))
390 self.edits[row] = [edit, edit2]
391 elif type == 'date':
392 # date editor
393 edit = DateControl(self, DateToText(value['Value'], self.cfg))
394 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
395 self.edits[row] = [edit]
396 else:
397 print 'warning: creating TextCtrl for %s' % type
398 edit = wx.TextCtrl(self, -1, StrConv(value['Value']), size = (200, -1))
399 self.sizer.Add(edit, (row + self.rowoffset, 4), (1, 4))
400 self.edits[row] = [edit]
401 self.sizer.Fit(self)
402 self.sizer.SetSizeHints(self)
403 self.sizer.Layout()
405 def OnContactSpinChange(self, evt):
406 row = evt.GetEventObject().row
407 self.edits[row][1].SetLabel(self.GetContactText(evt.GetInt()))
409 def OnContactButton(self, evt):
410 row = evt.GetEventObject().row
411 val = Wammu.Select.SelectContact(self, [] + self.values['contact']['ME'])
412 if val != -1:
413 self.edits[row][0].SetValue(val)
414 self.edits[row][1].SetLabel(self.GetContactText(val))
416 def GetContactText(self, val):
417 if val < 1:
418 return _('None')
419 else:
420 l = Wammu.Utils.SearchLocation(self.values['contact']['ME'], val)
421 if l == -1:
422 return _('Unknown')
423 else:
424 return self.values['contact']['ME'][l]['Name']
426 def DelTypeEdit(self, row):
427 for x in self.edits[row]:
428 if x is not None:
429 self.sizer.Detach(x)
430 x.Destroy()
431 self.edits[row] = [None]
433 def GetTypeEditValue(self, row):
434 if self.types[row] == 'date':
435 return TextToDate(self.edits[row][0].GetValue())
436 elif self.types[row] == 'datetime':
437 return datetime.datetime.combine(TextToDate(self.edits[row][1].GetValue()), TextToTime(self.edits[row][0].GetValue(), self.cfg))
438 elif self.types[row] == 'id':
439 return int(self.edits[row][0].GetValue(), 16)
440 elif self.types[row] in ['contact', 'bool', 'category', 'number']:
441 return int(self.edits[row][0].GetValue())
442 elif self.types[row] in ['phone', 'text']:
443 return UnicodeConv(self.edits[row][0].GetValue())
444 else:
445 return self.edits[row][0].GetValue()
447 def GetTypeEditVoiceTag(self, row):
448 if self.types[row] == 'phone':
449 return int(self.edits[row][1].GetValue(), 16)
450 return 0
452 def OnItemTypeChange(self, evt):
453 row = evt.GetEventObject().row
454 type = evt.GetString()
455 val = self.GetTypeEditValue(row)
456 self.DelTypeEdit(row)
457 self.AddTypeEdit(row, {'Type': type, 'Value':val})
459 def OnTypeChange(self, evt):
460 self.locationedit.SetValue(0)
462 def More(self, evt):
463 self.RemoveButtons()
464 self.AddEdit(self.rows)
465 self.AddButtons()
467 def Okay(self, evt):
468 if not self.Validate():
469 return
471 v = []
472 for row in range(self.rows):
473 t = self.fulltypes[row]
474 if t != '':
475 v.append({'Type' : t, 'Value' : self.GetTypeEditValue(row), 'VoiceTag' : self.GetTypeEditVoiceTag(row)})
477 self.entry['Entries'] = v
478 self.entry[self.type] = self.typeedit.GetValue()
479 self.entry['Location'] = self.locationedit.GetValue()
481 # Remember default type
482 if self.wasempty:
483 self.cfg.Write('/Defaults/Type-%s-%s' % (self.internalname, self.type),
484 self.entry[self.type])
486 self.EndModal(wx.ID_OK)
488 class ContactEditor(GenericEditor):
489 def __init__(self, parent, cfg, values, entry):
490 if entry == {}:
491 location = ''
492 else:
493 location = '%s:%d' % (entry['MemoryType'], entry['Location'])
494 GenericEditor.__init__(self, parent, cfg, values, entry, 'contact', _('contact'), location, 'MemoryType', _('Memory type'), Wammu.Data.ContactMemoryTypes, Wammu.Data.MemoryValueTypes)
496 class CalendarEditor(GenericEditor):
497 def __init__(self, parent, cfg, values, entry):
498 if entry == {}:
499 location = ''
500 else:
501 location = '%d' % entry['Location']
502 GenericEditor.__init__(self, parent, cfg, values, entry, 'calendar', _('calendar event'), location, 'Type', _('Event type'), Wammu.Data.CalendarTypes, Wammu.Data.CalendarValueTypes)
504 class TodoEditor(GenericEditor):
505 def __init__(self, parent, cfg, values, entry):
506 if entry == {}:
507 location = ''
508 else:
509 location = '%d' % entry['Location']
510 GenericEditor.__init__(self, parent, cfg, values, entry, 'todo', _('todo item'), location, 'Priority', _('Priority'), Wammu.Data.TodoPriorities, Wammu.Data.TodoValueTypes)
512 def Okay(self, evt):
513 self.entry['Type'] = 'MEMO'
514 GenericEditor.Okay(self, evt)