Adjust title underlines.
[wammu.git] / Wammu / Browser.py
blob67fca72a718e4c270afe0cde0bc026882812708d
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 _('State'),
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_call = wx.NewId()
199 self.popup_id_delete = wx.NewId()
200 self.popup_id_delete_selection = wx.NewId()
201 self.popup_id_duplicate = wx.NewId()
202 self.popup_id_reply = wx.NewId()
203 self.popup_id_backup_one = wx.NewId()
204 self.popup_id_backup_selection = wx.NewId()
205 self.popup_id_backup_all = wx.NewId()
207 self.BindEvents()
210 def BindEvents(self):
212 Bind various event handlers to events we need.
214 self.Bind(wx.EVT_LIST_ITEM_SELECTED,
215 self.OnItemSelected,
216 self)
217 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED,
218 self.OnItemActivated,
219 self)
220 self.Bind(wx.EVT_LIST_KEY_DOWN,
221 self.OnKey,
222 self)
223 self.Bind(wx.EVT_LIST_COL_CLICK,
224 self.OnColClick,
225 self)
226 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK,
227 self.OnRightClick,
228 self)
229 self.Bind(wx.EVT_MENU,
230 self.OnPopupSend,
231 id = self.popup_id_send)
232 self.Bind(wx.EVT_MENU,
233 self.OnPopupEdit,
234 id = self.popup_id_edit)
235 self.Bind(wx.EVT_MENU,
236 self.OnPopupMessage,
237 id = self.popup_id_message)
238 self.Bind(wx.EVT_MENU,
239 self.OnPopupCall,
240 id = self.popup_id_call)
241 self.Bind(wx.EVT_MENU,
242 self.OnPopupDelete,
243 id = self.popup_id_delete)
244 self.Bind(wx.EVT_MENU,
245 self.OnPopupDeleteSel,
246 id = self.popup_id_delete_selection)
247 self.Bind(wx.EVT_MENU,
248 self.OnPopupDuplicate,
249 id = self.popup_id_duplicate)
250 self.Bind(wx.EVT_MENU,
251 self.OnPopupReply,
252 id = self.popup_id_reply)
253 self.Bind(wx.EVT_MENU,
254 self.OnPopupBackupOne,
255 id = self.popup_id_backup_one)
256 self.Bind(wx.EVT_MENU,
257 self.OnPopupBackupSel,
258 id = self.popup_id_backup_selection)
259 self.Bind(wx.EVT_MENU,
260 self.OnPopupBackupAll,
261 id = self.popup_id_backup_all)
263 def ShowHeaders(self):
265 Updates which headers and keys should be show and displays them.
267 self.columns = COLUMN_INFO[self.type][0]
268 self.keys = COLUMN_INFO[self.type][1]
270 cnt = len(self.columns)
272 for i in range(cnt):
273 self.InsertColumn(i, self.columns[i])
275 # resize columns to fit content
277 # FIXME: this should be acquired better!
278 spc = 10
280 maxval = [0] * cnt
281 for i in range(cnt):
282 size = self.GetTextExtent(StrConv(self.columns[i]))[0]
283 # 16 bellow is for sort arrrow
284 if (size + 16 > maxval[i]):
285 maxval[i] = size + 16
287 for current in self.values:
288 for i in range(cnt):
289 size = self.GetTextExtent(StrConv(current[self.keys[i]]))
290 if (size[0] > maxval[i]):
291 maxval[i] = size[0]
292 for i in range(cnt - 1):
293 self.SetColumnWidth(i, maxval[i] + spc)
294 self.resizeLastColumn(maxval[cnt - 1] + spc)
296 def Filter(self, text, filter_type):
298 Filters content of browser by various expressions (type of expression
299 is defined by filter_type).
301 if text == '':
302 self.values = self.allvalues
303 else:
304 num = None
305 if text.isdigit():
306 num = int(text)
307 if filter_type == 0:
308 match = re.compile('.*%s.*' % re.escape(text), re.I)
309 elif filter_type == 1:
310 try:
311 match = re.compile(text, re.I)
312 except:
313 raise FilterException('Failed to compile regexp')
314 elif filter_type == 2:
315 text = text.replace('*', '__SEARCH_ALL__')
316 text = text.replace('?', '__SEARCH_ONE__')
317 text = re.escape(text)
318 text = text.replace('\\_\\_SEARCH\\_ALL\\_\\_', '.*')
319 text = text.replace('\\_\\_SEARCH\\_ONE\\_\\_', '.')
320 match = re.compile('.*%s.*' % text, re.I)
321 else:
322 raise Exception('Unsupported filter type %s!' % filter_type)
323 self.values = [item for item in self.allvalues
324 if Wammu.Utils.MatchesText(item, match, num)]
325 self.SetItemCount(len(self.values))
326 self.RefreshView()
327 self.ShowRow(0)
329 def Sorter(self, item1, item2):
331 Compare function for internal list of values.
333 if self.sortkey == 'Location' and type(item1[self.sortkey]) == type(''):
334 return self.sortorder * cmp(
335 int(item1[self.sortkey].split(',')[0]),
336 int(item2[self.sortkey].split(', ')[0]))
337 elif item1[self.sortkey] == None:
338 return -self.sortorder
339 elif item2[self.sortkey] == None:
340 return self.sortorder
341 return self.sortorder * cmp(item1[self.sortkey], item2[self.sortkey])
343 def ShowLocation(self, loc, second = None):
345 Shows row which is stored on defined location. Search can be extended
346 by specifiyng second tupe of search attribute and value.
348 result = Wammu.Utils.SearchLocation(self.values, loc, second)
349 if result != -1:
350 self.ShowRow(result)
352 def ShowRow(self, index):
354 Activates id-th row.
356 if (self.GetItemCount() > index
357 and index >= 0
358 and self.GetCountPerPage() > 0):
359 self.itemno = index
361 while self.GetFirstSelected() != -1:
362 self.SetItemState(self.GetFirstSelected(), 0, wx.LIST_STATE_SELECTED)
364 self.SetItemState(index,
365 wx.LIST_STATE_FOCUSED | wx.LIST_STATE_SELECTED,
366 wx.LIST_STATE_FOCUSED | wx.LIST_STATE_SELECTED)
367 self.EnsureVisible(index)
368 else:
369 evt = Wammu.Events.ShowEvent(data = None)
370 wx.PostEvent(self.win, evt)
372 def Change(self, newtype, values):
374 Change type of browser component.
376 if self.type != '':
377 self.cfg.Write('/BrowserSortKey/%s' % self.type,
378 self.sortkey)
379 self.cfg.WriteInt('/BrowserSortOrder/%s' % self.type,
380 self.sortorder)
381 self.type = newtype
382 self.values = values
383 self.allvalues = values
384 self.sortkey = ''
385 self.sortorder = 1
386 self.ClearAll()
387 self.SetItemCount(len(values))
388 self.ShowHeaders()
389 # restore sort order
390 found = False
391 readsort = self.cfg.Read('/BrowserSortKey/%s' % self.type)
392 readorder = self.cfg.ReadInt('/BrowserSortOrder/%s' % self.type)
393 for i in range(len(self.keys)):
394 if self.keys[i] == readsort:
395 if readorder == -1:
396 self.sortkey = readsort
397 self.Resort(i)
398 found = True
399 if not found:
400 self.Resort(0)
402 def Resort(self, col):
404 Changes sort order of listing.
406 # remember show item
407 try:
408 item = self.values[self.itemno]
409 except IndexError:
410 item = None
411 # find keys and order
412 nextsort = self.keys[col]
413 if nextsort == self.sortkey:
414 self.sortorder = -1 * self.sortorder
415 else:
416 self.sortorder = 1
417 self.sortkey = nextsort
419 # do the real sort
420 self.values.sort(self.Sorter)
422 # set image
423 for i in range(self.GetColumnCount()):
424 self.ClearColumnImage(i)
425 if self.sortorder == 1:
426 image = self.downarrow
427 else:
428 image = self.uparrow
429 self.SetColumnImage(col, image)
430 self.RefreshView()
432 if item != None:
433 self.ShowRow(self.values.index(item))
435 def RefreshView(self):
437 Refresh displayed items.
439 if self.GetItemCount() != 0:
440 top = self.GetTopItem()
441 if top < 0:
442 top = 0
443 count = self.GetCountPerPage()
444 totalcount = self.GetItemCount()
445 if count < 0:
446 count = totalcount
447 last = min(totalcount - 1, top + count)
448 self.RefreshItems(top, last)
451 def OnKey(self, evt):
453 Key handler which catches delete key for deletion of current item and
454 R/r key for message reply.
456 if evt.GetKeyCode() == wx.WXK_DELETE:
457 self.DoSelectedDelete()
458 elif evt.GetKeyCode() in [114, 82]:
459 self.DoReply()
461 def DoSelectedDelete(self):
463 Delete selected message.
465 lst = []
466 index = self.GetFirstSelected()
467 while index != -1:
468 lst.append(self.values[index])
469 index = self.GetNextSelected(index)
470 self.DoDelete(lst)
472 def DoDelete(self, lst):
474 Send delete event to parent.
476 evt = Wammu.Events.DeleteEvent(lst = lst)
477 wx.PostEvent(self.win, evt)
479 def DoBackup(self, lst):
481 Send backup event to parent.
483 evt = Wammu.Events.BackupEvent(lst = lst)
484 wx.PostEvent(self.win, evt)
486 def DoReply(self):
488 Send reply event to parent.
490 evt = Wammu.Events.ReplyEvent(data = self.values[self.GetFocusedItem()])
491 wx.PostEvent(self.win, evt)
493 def OnRightClick(self, evt):
495 Handle right click - show context menu with correct options for
496 current type of listing.
498 if self.type == 'info':
499 return
500 self.popup_index = evt.m_itemIndex
502 # make a menu
503 menu = wx.Menu()
505 # add some items
506 if self.popup_index != -1 and self.type == 'message':
507 if self.values[evt.m_itemIndex]['State'] == 'Sent':
508 menu.Append(self.popup_id_send, _('Resend'))
509 if self.values[evt.m_itemIndex]['State'] == 'UnSent':
510 menu.Append(self.popup_id_send, _('Send'))
511 if (self.values[evt.m_itemIndex]['State'] == 'Read'
512 or self.values[evt.m_itemIndex]['State'] == 'UnRead'):
513 menu.Append(self.popup_id_reply, _('Reply'))
514 if self.values[evt.m_itemIndex]['Number'] != '':
515 menu.Append(self.popup_id_call, _('Call'))
516 menu.AppendSeparator()
518 if self.popup_index != -1 and self.type in ['contact', 'call']:
519 menu.Append(self.popup_id_message, _('Send message'))
520 menu.Append(self.popup_id_call, _('Call'))
521 menu.AppendSeparator()
523 if self.popup_index != -1 and not self.type in ['call', 'message']:
524 menu.Append(self.popup_id_edit, _('Edit'))
525 if self.popup_index != -1 and not self.type in ['call']:
526 menu.Append(self.popup_id_duplicate, _('Duplicate'))
527 menu.AppendSeparator()
529 if self.popup_index != -1:
530 menu.Append(self.popup_id_delete, _('Delete current'))
531 menu.Append(self.popup_id_delete_selection, _('Delete selected'))
533 menu.AppendSeparator()
534 if self.popup_index != -1:
535 menu.Append(self.popup_id_backup_one, _('Backup current'))
536 menu.Append(self.popup_id_backup_selection, _('Backup selected'))
537 menu.Append(self.popup_id_backup_all, _('Backup all'))
539 # Popup the menu. If an item is selected then its handler
540 # will be called before PopupMenu returns.
541 self.PopupMenu(menu, evt.GetPoint())
543 def OnPopupDuplicate(self, event):
544 evt = Wammu.Events.DuplicateEvent(data = self.values[self.popup_index])
545 wx.PostEvent(self.win, evt)
547 def OnPopupReply(self, event):
548 evt = Wammu.Events.ReplyEvent(data = self.values[self.popup_index])
549 wx.PostEvent(self.win, evt)
551 def OnPopupSend(self, event):
552 evt = Wammu.Events.SendEvent(data = self.values[self.popup_index])
553 wx.PostEvent(self.win, evt)
555 def OnPopupCall(self, event):
556 evt = Wammu.Events.CallEvent(data = self.values[self.popup_index])
557 wx.PostEvent(self.win, evt)
559 def OnPopupMessage(self, event):
560 evt = Wammu.Events.MessageEvent(data = self.values[self.popup_index])
561 wx.PostEvent(self.win, evt)
563 def OnPopupEdit(self, event):
564 evt = Wammu.Events.EditEvent(data = self.values[self.popup_index])
565 wx.PostEvent(self.win, evt)
567 def OnPopupDelete(self, event):
568 self.DoDelete([self.values[self.popup_index]])
570 def OnPopupDeleteSel(self, event):
571 self.DoSelectedDelete()
573 def OnPopupBackupOne(self, event):
574 self.DoBackup([self.values[self.popup_index]])
576 def OnPopupBackupSel(self, event):
577 item_list = []
578 index = self.GetFirstSelected()
579 while index != -1:
580 item_list.append(self.values[index])
581 index = self.GetNextSelected(index)
582 self.DoBackup(item_list)
584 def OnPopupBackupAll(self, event):
585 self.DoBackup(self.values)
587 def OnColClick(self, evt):
588 self.Resort(evt.GetColumn())
590 def OnItemSelected(self, event):
591 self.itemno = event.m_itemIndex
592 evt = Wammu.Events.ShowEvent(data = self.values[event.m_itemIndex])
593 wx.PostEvent(self.win, evt)
595 def OnItemActivated(self, event):
596 evt = Wammu.Events.EditEvent(data = self.values[event.m_itemIndex])
597 wx.PostEvent(self.win, evt)
599 def getColumnText(self, index, col):
600 item = self.GetItem(index, col)
601 return item.GetText()
603 def OnGetItemText(self, item, col):
605 Get item text.
607 if item >= len(self.values):
608 return None
609 return StrConv(self.values[item][self.keys[col]])
611 def OnGetItemAttr(self, item):
613 Get item attributes - highlight synced items, make odd and even rows
614 different.
616 if self.values[item]['Synced']:
617 if item % 2 == 1:
618 return self.attr1
619 else:
620 return self.attr2
621 if item % 2 == 1:
622 return self.attr3
623 else:
624 return self.attr4