Add Turkish docs
[wammu.git] / Wammu / Browser.py
blob861b9818f550f5765a4bf738c380202a5c1de18a
1 # -*- coding: UTF-8 -*-
2 # vim: expandtab sw=4 ts=4 sts=4:
3 '''
4 Wammu - Phone manager
5 Items browser
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 import re
28 import Wammu
29 import Wammu.Events
30 import Wammu.Utils
31 import Wammu.Paths
32 from Wammu.Locales import StrConv
34 import wx.lib.mixins.listctrl
36 COLUMN_INFO = {
37 'info':
40 _('Name'),
41 _('Value')
44 'Name',
45 'Value'
48 'contact':
51 _('Location'),
52 _('Memory'),
53 _('Name'),
54 _('Number')
57 'Location',
58 'MemoryType',
59 'Name',
60 'Number'
63 'call':
66 _('Location'),
67 _('Type'),
68 _('Name'),
69 _('Number'),
70 _('Date')
73 'Location',
74 'MemoryType',
75 'Name',
76 'Number',
77 'Date'
80 'message':
83 _('Location'),
84 _('Status'),
85 _('Number'),
86 _('Date'),
87 _('Text')
90 'Location',
91 'State',
92 'Number',
93 'DateTime',
94 'Text'
97 'todo':
100 _('Location'),
101 _('Completed'),
102 _('Priority'),
103 _('Text'),
104 _('Date')
107 'Location',
108 'Completed',
109 'Priority',
110 'Text',
111 'Date'
114 'calendar':
117 _('Location'),
118 _('Type'),
119 _('Start'),
120 _('End'),
121 _('Text'),
122 _('Alarm'),
123 _('Recurrence')
126 'Location',
127 'Type',
128 'Start',
129 'End',
130 'Text',
131 'Alarm',
132 'Recurrence'
137 class FilterException(Exception):
139 Exception which occurs when there is something wrong in filtering
140 expression.
142 pass
144 class Browser(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
146 Generic class for browsing values.
148 def __init__(self, parent, win, cfg):
149 wx.ListCtrl.__init__(self,
150 parent,
152 style = wx.LC_REPORT |
153 wx.LC_VIRTUAL |
154 wx.LC_HRULES |
155 wx.LC_VRULES)
156 self.win = win
157 self.cfg = cfg
159 self.itemno = -1
160 self.type = ''
161 self.values = []
162 self.allvalues = []
163 self.sortkey = ''
164 self.sortorder = 1
165 self.columns = []
166 self.keys = []
167 self.popup_index = -1
169 color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)
171 self.attr1 = wx.ListItemAttr()
173 self.attr2 = wx.ListItemAttr()
174 self.attr2.SetBackgroundColour(color)
176 self.attr3 = wx.ListItemAttr()
177 fnt = self.attr3.GetFont()
178 fnt.SetStyle(wx.FONTSTYLE_ITALIC)
179 self.attr3.SetFont(fnt)
181 self.attr4 = wx.ListItemAttr()
182 self.attr4.SetBackgroundColour(color)
183 self.attr4.SetFont(fnt)
185 image_list = wx.ImageList(16, 16)
186 down_bitmap = wx.Bitmap(Wammu.Paths.MiscPath('downarrow'))
187 up_bitmap = wx.Bitmap(Wammu.Paths.MiscPath('uparrow'))
188 self.downarrow = image_list.Add(down_bitmap)
189 self.uparrow = image_list.Add(up_bitmap)
190 self.AssignImageList(image_list, wx.IMAGE_LIST_SMALL)
192 wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)
194 # Create IDs for popup menu
195 self.popup_id_send = wx.NewId()
196 self.popup_id_edit = wx.NewId()
197 self.popup_id_message = wx.NewId()
198 self.popup_id_contact = wx.NewId()
199 self.popup_id_call = wx.NewId()
200 self.popup_id_delete = wx.NewId()
201 self.popup_id_delete_selection = wx.NewId()
202 self.popup_id_duplicate = wx.NewId()
203 self.popup_id_reply = wx.NewId()
204 self.popup_id_backup_one = wx.NewId()
205 self.popup_id_backup_selection = wx.NewId()
206 self.popup_id_backup_all = wx.NewId()
208 self.BindEvents()
211 def BindEvents(self):
213 Bind various event handlers to events we need.
215 self.Bind(wx.EVT_LIST_ITEM_SELECTED,
216 self.OnItemSelected,
217 self)
218 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED,
219 self.OnItemActivated,
220 self)
221 self.Bind(wx.EVT_LIST_KEY_DOWN,
222 self.OnKey,
223 self)
224 self.Bind(wx.EVT_LIST_COL_CLICK,
225 self.OnColClick,
226 self)
227 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK,
228 self.OnRightClick,
229 self)
230 self.Bind(wx.EVT_MENU,
231 self.OnPopupSend,
232 id = self.popup_id_send)
233 self.Bind(wx.EVT_MENU,
234 self.OnPopupEdit,
235 id = self.popup_id_edit)
236 self.Bind(wx.EVT_MENU,
237 self.OnPopupMessage,
238 id = self.popup_id_message)
239 self.Bind(wx.EVT_MENU,
240 self.OnPopupContact,
241 id = self.popup_id_contact)
242 self.Bind(wx.EVT_MENU,
243 self.OnPopupCall,
244 id = self.popup_id_call)
245 self.Bind(wx.EVT_MENU,
246 self.OnPopupDelete,
247 id = self.popup_id_delete)
248 self.Bind(wx.EVT_MENU,
249 self.OnPopupDeleteSel,
250 id = self.popup_id_delete_selection)
251 self.Bind(wx.EVT_MENU,
252 self.OnPopupDuplicate,
253 id = self.popup_id_duplicate)
254 self.Bind(wx.EVT_MENU,
255 self.OnPopupReply,
256 id = self.popup_id_reply)
257 self.Bind(wx.EVT_MENU,
258 self.OnPopupBackupOne,
259 id = self.popup_id_backup_one)
260 self.Bind(wx.EVT_MENU,
261 self.OnPopupBackupSel,
262 id = self.popup_id_backup_selection)
263 self.Bind(wx.EVT_MENU,
264 self.OnPopupBackupAll,
265 id = self.popup_id_backup_all)
267 def ShowHeaders(self):
269 Updates which headers and keys should be show and displays them.
271 self.columns = COLUMN_INFO[self.type][0]
272 self.keys = COLUMN_INFO[self.type][1]
274 cnt = len(self.columns)
276 for i in range(cnt):
277 self.InsertColumn(i, self.columns[i])
279 # resize columns to fit content
281 # FIXME: this should be acquired better!
282 spc = 10
284 maxval = [0] * cnt
285 for i in range(cnt):
286 size = self.GetTextExtent(StrConv(self.columns[i]))[0]
287 # 16 bellow is for sort arrrow
288 if (size + 16 > maxval[i]):
289 maxval[i] = size + 16
291 for current in self.values:
292 for i in range(cnt):
293 size = self.GetTextExtent(StrConv(current[self.keys[i]]))
294 if (size[0] > maxval[i]):
295 maxval[i] = size[0]
296 for i in range(cnt - 1):
297 self.SetColumnWidth(i, maxval[i] + spc)
298 self.resizeLastColumn(maxval[cnt - 1] + spc)
300 def Filter(self, text, filter_type):
302 Filters content of browser by various expressions (type of expression
303 is defined by filter_type).
305 if text == '':
306 self.values = self.allvalues
307 else:
308 num = None
309 if text.isdigit():
310 num = int(text)
311 if filter_type == 0:
312 match = re.compile('.*%s.*' % re.escape(text), re.I)
313 elif filter_type == 1:
314 try:
315 match = re.compile(text, re.I)
316 except:
317 raise FilterException('Failed to compile regexp')
318 elif filter_type == 2:
319 text = text.replace('*', '__SEARCH_ALL__')
320 text = text.replace('?', '__SEARCH_ONE__')
321 text = re.escape(text)
322 text = text.replace('\\_\\_SEARCH\\_ALL\\_\\_', '.*')
323 text = text.replace('\\_\\_SEARCH\\_ONE\\_\\_', '.')
324 match = re.compile('.*%s.*' % text, re.I)
325 else:
326 raise Exception('Unsupported filter type %s!' % filter_type)
327 self.values = [item for item in self.allvalues
328 if Wammu.Utils.MatchesText(item, match, num)]
329 self.SetItemCount(len(self.values))
330 self.RefreshView()
331 self.ShowRow(0)
333 def Sorter(self, item1, item2):
335 Compare function for internal list of values.
337 if self.sortkey == 'Location' and type(item1[self.sortkey]) == type(''):
338 return self.sortorder * cmp(
339 int(item1[self.sortkey].split(',')[0]),
340 int(item2[self.sortkey].split(', ')[0]))
341 elif item1[self.sortkey] == None:
342 return -self.sortorder
343 elif item2[self.sortkey] == None:
344 return self.sortorder
345 return self.sortorder * cmp(item1[self.sortkey], item2[self.sortkey])
347 def ShowLocation(self, loc, second = None):
349 Shows row which is stored on defined location. Search can be extended
350 by specifiyng second tupe of search attribute and value.
352 result = Wammu.Utils.SearchLocation(self.values, loc, second)
353 if result != -1:
354 self.ShowRow(result)
356 def ShowRow(self, index):
358 Activates id-th row.
360 if (self.GetItemCount() > index
361 and index >= 0
362 and self.GetCountPerPage() > 0):
363 self.itemno = index
365 while self.GetFirstSelected() != -1:
366 self.SetItemState(self.GetFirstSelected(), 0, wx.LIST_STATE_SELECTED)
368 self.SetItemState(index,
369 wx.LIST_STATE_FOCUSED | wx.LIST_STATE_SELECTED,
370 wx.LIST_STATE_FOCUSED | wx.LIST_STATE_SELECTED)
371 self.EnsureVisible(index)
372 else:
373 evt = Wammu.Events.ShowEvent(data = None)
374 wx.PostEvent(self.win, evt)
376 def Change(self, newtype, values):
378 Change type of browser component.
380 if self.type != '':
381 self.cfg.Write('/BrowserSortKey/%s' % self.type,
382 self.sortkey)
383 self.cfg.WriteInt('/BrowserSortOrder/%s' % self.type,
384 self.sortorder)
385 self.type = newtype
386 self.values = values
387 self.allvalues = values
388 self.sortkey = ''
389 self.sortorder = 1
390 self.ClearAll()
391 self.SetItemCount(len(values))
392 self.ShowHeaders()
393 # restore sort order
394 found = False
395 readsort = self.cfg.Read('/BrowserSortKey/%s' % self.type)
396 readorder = self.cfg.ReadInt('/BrowserSortOrder/%s' % self.type)
397 for i in range(len(self.keys)):
398 if self.keys[i] == readsort:
399 if readorder == -1:
400 self.sortkey = readsort
401 self.Resort(i)
402 found = True
403 if not found:
404 self.Resort(0)
406 def Resort(self, col):
408 Changes sort order of listing.
410 # remember show item
411 try:
412 item = self.values[self.itemno]
413 except IndexError:
414 item = None
415 # find keys and order
416 nextsort = self.keys[col]
417 if nextsort == self.sortkey:
418 self.sortorder = -1 * self.sortorder
419 else:
420 self.sortorder = 1
421 self.sortkey = nextsort
423 # do the real sort
424 self.values.sort(self.Sorter)
426 # set image
427 for i in range(self.GetColumnCount()):
428 self.ClearColumnImage(i)
429 if self.sortorder == 1:
430 image = self.downarrow
431 else:
432 image = self.uparrow
433 self.SetColumnImage(col, image)
434 self.RefreshView()
436 if item != None:
437 self.ShowRow(self.values.index(item))
439 def RefreshView(self):
441 Refresh displayed items.
443 if self.GetItemCount() != 0:
444 top = self.GetTopItem()
445 if top < 0:
446 top = 0
447 count = self.GetCountPerPage()
448 totalcount = self.GetItemCount()
449 if count < 0:
450 count = totalcount
451 last = min(totalcount - 1, top + count)
452 self.RefreshItems(top, last)
455 def OnKey(self, evt):
457 Key handler which catches delete key for deletion of current item and
458 R/r key for message reply.
460 if evt.GetKeyCode() == wx.WXK_DELETE:
461 self.DoSelectedDelete()
462 elif evt.GetKeyCode() in [114, 82]:
463 self.DoReply()
465 def DoSelectedDelete(self):
467 Delete selected message.
469 lst = []
470 index = self.GetFirstSelected()
471 while index != -1:
472 lst.append(self.values[index])
473 index = self.GetNextSelected(index)
474 self.DoDelete(lst)
476 def DoDelete(self, lst):
478 Send delete event to parent.
480 evt = Wammu.Events.DeleteEvent(lst = lst)
481 wx.PostEvent(self.win, evt)
483 def DoBackup(self, lst):
485 Send backup event to parent.
487 evt = Wammu.Events.BackupEvent(lst = lst)
488 wx.PostEvent(self.win, evt)
490 def DoReply(self):
492 Send reply event to parent.
494 evt = Wammu.Events.ReplyEvent(data = self.values[self.GetFocusedItem()])
495 wx.PostEvent(self.win, evt)
497 def OnRightClick(self, evt):
499 Handle right click - show context menu with correct options for
500 current type of listing.
502 if self.type == 'info':
503 return
504 self.popup_index = evt.m_itemIndex
506 # make a menu
507 menu = wx.Menu()
509 # add some items
510 if self.popup_index != -1 and self.type == 'message':
511 if self.values[evt.m_itemIndex]['State'] == 'Sent':
512 menu.Append(self.popup_id_send, _('Resend'))
513 if self.values[evt.m_itemIndex]['State'] == 'UnSent':
514 menu.Append(self.popup_id_send, _('Send'))
515 if (self.values[evt.m_itemIndex]['State'] == 'Read'
516 or self.values[evt.m_itemIndex]['State'] == 'UnRead'):
517 menu.Append(self.popup_id_reply, _('Reply'))
518 if self.values[evt.m_itemIndex]['Number'] != '':
519 menu.Append(self.popup_id_call, _('Call'))
520 menu.AppendSeparator()
522 if self.popup_index != -1 and self.type in ['contact', 'call']:
523 menu.Append(self.popup_id_message, _('Send message'))
524 menu.Append(self.popup_id_call, _('Call'))
525 if self.popup_index != -1 and self.type in ['call']:
526 menu.Append(self.popup_id_contact, _('Store as new contact'))
527 menu.AppendSeparator()
529 if self.popup_index != -1 and not self.type in ['call', 'message']:
530 menu.Append(self.popup_id_edit, _('Edit'))
531 if self.popup_index != -1 and not self.type in ['call']:
532 menu.Append(self.popup_id_duplicate, _('Duplicate'))
533 menu.AppendSeparator()
535 if self.popup_index != -1:
536 menu.Append(self.popup_id_delete, _('Delete current'))
537 menu.Append(self.popup_id_delete_selection, _('Delete selected'))
539 menu.AppendSeparator()
540 if self.popup_index != -1:
541 menu.Append(self.popup_id_backup_one, _('Backup current'))
542 menu.Append(self.popup_id_backup_selection, _('Backup selected'))
543 menu.Append(self.popup_id_backup_all, _('Backup all'))
545 # Popup the menu. If an item is selected then its handler
546 # will be called before PopupMenu returns.
547 self.PopupMenu(menu, evt.GetPoint())
549 def OnPopupDuplicate(self, event):
550 evt = Wammu.Events.DuplicateEvent(data = self.values[self.popup_index])
551 wx.PostEvent(self.win, evt)
553 def OnPopupReply(self, event):
554 evt = Wammu.Events.ReplyEvent(data = self.values[self.popup_index])
555 wx.PostEvent(self.win, evt)
557 def OnPopupSend(self, event):
558 evt = Wammu.Events.SendEvent(data = self.values[self.popup_index])
559 wx.PostEvent(self.win, evt)
561 def OnPopupCall(self, event):
562 evt = Wammu.Events.CallEvent(data = self.values[self.popup_index])
563 wx.PostEvent(self.win, evt)
565 def OnPopupMessage(self, event):
566 evt = Wammu.Events.MessageEvent(data = self.values[self.popup_index])
567 wx.PostEvent(self.win, evt)
569 def OnPopupContact(self, event):
570 data = self.values[self.popup_index]
571 data['Location'] = 0
572 data['MemoryType'] = 'ME'
573 evt = Wammu.Events.EditEvent(data = data)
574 wx.PostEvent(self.win, evt)
576 def OnPopupEdit(self, event):
577 evt = Wammu.Events.EditEvent(data = self.values[self.popup_index])
578 wx.PostEvent(self.win, evt)
580 def OnPopupDelete(self, event):
581 self.DoDelete([self.values[self.popup_index]])
583 def OnPopupDeleteSel(self, event):
584 self.DoSelectedDelete()
586 def OnPopupBackupOne(self, event):
587 self.DoBackup([self.values[self.popup_index]])
589 def OnPopupBackupSel(self, event):
590 item_list = []
591 index = self.GetFirstSelected()
592 while index != -1:
593 item_list.append(self.values[index])
594 index = self.GetNextSelected(index)
595 self.DoBackup(item_list)
597 def OnPopupBackupAll(self, event):
598 self.DoBackup(self.values)
600 def OnColClick(self, evt):
601 self.Resort(evt.GetColumn())
603 def OnItemSelected(self, event):
604 self.itemno = event.m_itemIndex
605 evt = Wammu.Events.ShowEvent(data = self.values[event.m_itemIndex])
606 wx.PostEvent(self.win, evt)
608 def OnItemActivated(self, event):
609 evt = Wammu.Events.EditEvent(data = self.values[event.m_itemIndex])
610 wx.PostEvent(self.win, evt)
612 def getColumnText(self, index, col):
613 item = self.GetItem(index, col)
614 return item.GetText()
616 def OnGetItemText(self, item, col):
618 Get item text.
620 if item >= len(self.values):
621 return None
622 return StrConv(self.values[item][self.keys[col]])
624 def OnGetItemAttr(self, item):
626 Get item attributes - highlight synced items, make odd and even rows
627 different.
629 if self.values[item]['Synced']:
630 if item % 2 == 1:
631 return self.attr1
632 else:
633 return self.attr2
634 if item % 2 == 1:
635 return self.attr3
636 else:
637 return self.attr4