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 - 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
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 *
39 import Wammu
.PhoneValidator
40 from Wammu
.Locales
import StrConv
, UnicodeConv
43 def TextToTime(txt
, config
):
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]))
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
):
64 return time
.isoformat()
66 return config
.Read('/Wammu/DefaultTime')
68 def DateToText(date
, config
):
74 return date
.strftime('%d.%m.%Y')
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
):
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()
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')))
100 (self
.textCtrl
, 1, wx
.EXPAND
),
101 (self
.bCtrl
, 1, wx
.EXPAND
),
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()
113 return self
.textCtrl
.Validate()
115 def OnFocus(self
,evt
):
116 self
.textCtrl
.SetFocus()
119 def OnButton(self
,evt
):
120 self
.pop
= CalendarPopup(self
)
121 txtValue
= self
.GetValue()
122 dmy
= txtValue
.split('.')
129 if m
>= 0 and m
< 12:
131 self
.pop
.cal
.SetDate(DateTimeFromDMY(d
,m
,y
))
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
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
152 self
.pop
.MoveXY(pos
.x
,pos
.y
)
153 wx
.calendar
.EVT_CALENDAR_DAY(self
, self
.pop
.cal
.GetId(),self
.OnCalSelected
)
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
)
165 return self
.textCtrl
.GetValue()
167 def OnCalSelected(self
,evt
):
168 date
= self
.pop
.cal
.GetDate()
169 self
.SetValue('%02d.%02d.%04d' % (
176 class ContactEdit(wx
.Panel
):
180 def __init__(self
, parent
, val
, values
):
181 wx
.Panel
.__init
__(self
, parent
, -1)
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
)
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
)
196 self
.SetAutoLayout(True)
197 self
.SetSizer(self
.sizer
)
199 def OnChange(self
, evt
):
200 self
.txt
.SetLabel(self
.GetText(self
.edit
.GetValue()))
202 # self.sizer.SetSizeHints(self)
204 def OnContacts(self
, evt
):
205 i
= Wammu
.Select
.SelectContact(self
, self
.values
)
209 def GetText(self
, val
):
213 l
= Wammu
.Utils
.SearchLocation(self
.values
, val
)
217 return self
.values
[l
]['Name']
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
):
232 title
= _('Creating new %s') % name
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
)
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)
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))
264 self
.Bind(wx
.EVT_TEXT
, self
.OnTypeChange
, self
.typeedit
)
271 for x
in range(self
.cfg
.ReadInt('/Wammu/DefaultEntries')):
272 entrytype
= self
.cfg
.Read('/Defaults/Entry-%s-%d' % (self
.internalname
, x
))
274 self
.AddEdit(x
, {'Type': entrytype
, 'Value': '', 'VoiceTag': 0, 'AddError': 0})
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
)
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
)
300 self
.sizer
.SetSizeHints(self
)
303 def RemoveButtons(self
):
304 self
.sizer
.Detach(self
.button_sizer
)
306 def AddEdit(self
, row
, value
= {'Type':'', 'Value':''}):
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))
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:
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))
329 v
= hex(value
['VoiceTag'])
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
]
340 val
= bool(value
['Value'])
343 edit
= wx
.CheckBox(self
, -1, '', size
= (200, -1))
345 self
.sizer
.Add(edit
, (row
+ self
.rowoffset
, 4), (1, 4))
346 self
.edits
[row
] = [edit
]
347 elif type == 'contact':
350 val
= int(value
['Value'])
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))
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))
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
)
365 v
= hex(value
['Value'])
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':
375 # FIXME: category should be selectable
377 val
= int(value
['Value'])
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':
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
]
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
]
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
]
403 self
.sizer
.SetSizeHints(self
)
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'])
414 self
.edits
[row
][0].SetValue(val
)
415 self
.edits
[row
][1].SetLabel(self
.GetContactText(val
))
417 def GetContactText(self
, val
):
421 l
= Wammu
.Utils
.SearchLocation(self
.values
['contact']['ME'], val
)
425 return self
.values
['contact']['ME'][l
]['Name']
427 def DelTypeEdit(self
, row
):
428 for x
in self
.edits
[row
]:
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())
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)
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)
465 self
.AddEdit(self
.rows
)
469 if not self
.Validate():
473 for row
in range(self
.rows
):
474 t
= self
.fulltypes
[row
]
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
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
):
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
):
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
):
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
)
514 self
.entry
['Type'] = 'MEMO'
515 GenericEditor
.Okay(self
, evt
)