1 # -*- coding: UTF-8 -*-
2 # vim: expandtab sw=4 ts=4 sts=4:
7 __author__
= 'Michal Čihař'
8 __email__
= 'michal@cihar.com'
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
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
27 from wx
import DateTimeFromDMY
, DateTime_Today
29 import wx
.lib
.masked
.timectrl
30 from wx
.lib
.masked
import Ctrl
as maskedCtrl
31 from Wammu
.Paths
import *
38 import Wammu
.PhoneValidator
39 from Wammu
.Locales
import StrConv
, UnicodeConv
42 def TextToTime(txt
, config
):
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]))
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
):
63 return time
.isoformat()
65 return config
.Read('/Wammu/DefaultTime')
67 def DateToText(date
, config
):
73 return date
.strftime('%d.%m.%Y')
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
):
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()
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')))
99 (self
.textCtrl
, 1, wx
.EXPAND
),
100 (self
.bCtrl
, 1, wx
.EXPAND
),
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()
112 return self
.textCtrl
.Validate()
114 def OnFocus(self
,evt
):
115 self
.textCtrl
.SetFocus()
118 def OnButton(self
,evt
):
119 self
.pop
= CalendarPopup(self
)
120 txtValue
= self
.GetValue()
121 dmy
= txtValue
.split('.')
128 if m
>= 0 and m
< 12:
130 self
.pop
.cal
.SetDate(DateTimeFromDMY(d
,m
,y
))
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
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
151 self
.pop
.MoveXY(pos
.x
,pos
.y
)
152 wx
.calendar
.EVT_CALENDAR_DAY(self
, self
.pop
.cal
.GetId(),self
.OnCalSelected
)
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
)
164 return self
.textCtrl
.GetValue()
166 def OnCalSelected(self
,evt
):
167 date
= self
.pop
.cal
.GetDate()
168 self
.SetValue('%02d.%02d.%04d' % (
175 class ContactEdit(wx
.Panel
):
179 def __init__(self
, parent
, val
, values
):
180 wx
.Panel
.__init
__(self
, parent
, -1)
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
)
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
)
195 self
.SetAutoLayout(True)
196 self
.SetSizer(self
.sizer
)
198 def OnChange(self
, evt
):
199 self
.txt
.SetLabel(self
.GetText(self
.edit
.GetValue()))
201 # self.sizer.SetSizeHints(self)
203 def OnContacts(self
, evt
):
204 i
= Wammu
.Select
.SelectContact(self
, self
.values
)
208 def GetText(self
, val
):
212 l
= Wammu
.Utils
.SearchLocation(self
.values
, val
)
216 return self
.values
[l
]['Name']
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
):
231 title
= _('Creating new %s') % name
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
)
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)
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))
263 self
.Bind(wx
.EVT_TEXT
, self
.OnTypeChange
, self
.typeedit
)
270 for x
in range(self
.cfg
.ReadInt('/Wammu/DefaultEntries')):
271 entrytype
= self
.cfg
.Read('/Defaults/Entry-%s-%d' % (self
.internalname
, x
))
273 self
.AddEdit(x
, {'Type': entrytype
, 'Value': '', 'VoiceTag': 0, 'AddError': 0})
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
)
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
)
299 self
.sizer
.SetSizeHints(self
)
302 def RemoveButtons(self
):
303 self
.sizer
.Detach(self
.button_sizer
)
305 def AddEdit(self
, row
, value
= {'Type':'', 'Value':''}):
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))
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:
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))
328 v
= hex(value
['VoiceTag'])
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
]
339 val
= bool(value
['Value'])
342 edit
= wx
.CheckBox(self
, -1, '', size
= (200, -1))
344 self
.sizer
.Add(edit
, (row
+ self
.rowoffset
, 4), (1, 4))
345 self
.edits
[row
] = [edit
]
346 elif type == 'contact':
349 val
= int(value
['Value'])
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))
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))
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
)
364 v
= hex(value
['Value'])
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':
374 # FIXME: category should be selectable
376 val
= int(value
['Value'])
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':
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
]
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
]
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
]
402 self
.sizer
.SetSizeHints(self
)
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'])
413 self
.edits
[row
][0].SetValue(val
)
414 self
.edits
[row
][1].SetLabel(self
.GetContactText(val
))
416 def GetContactText(self
, val
):
420 l
= Wammu
.Utils
.SearchLocation(self
.values
['contact']['ME'], val
)
424 return self
.values
['contact']['ME'][l
]['Name']
426 def DelTypeEdit(self
, row
):
427 for x
in self
.edits
[row
]:
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())
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)
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)
464 self
.AddEdit(self
.rows
)
468 if not self
.Validate():
472 for row
in range(self
.rows
):
473 t
= self
.fulltypes
[row
]
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
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
):
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
):
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
):
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
)
513 self
.entry
['Type'] = 'MEMO'
514 GenericEditor
.Okay(self
, evt
)