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
32 from Wammu
.Locales
import StrConv
34 import wx
.lib
.mixins
.listctrl
137 class FilterException(Exception):
139 Exception which occurs when there is something wrong in filtering
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
,
152 style
= wx
.LC_REPORT |
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()
211 def BindEvents(self
):
213 Bind various event handlers to events we need.
215 self
.Bind(wx
.EVT_LIST_ITEM_SELECTED
,
218 self
.Bind(wx
.EVT_LIST_ITEM_ACTIVATED
,
219 self
.OnItemActivated
,
221 self
.Bind(wx
.EVT_LIST_KEY_DOWN
,
224 self
.Bind(wx
.EVT_LIST_COL_CLICK
,
227 self
.Bind(wx
.EVT_LIST_ITEM_RIGHT_CLICK
,
230 self
.Bind(wx
.EVT_MENU
,
232 id = self
.popup_id_send
)
233 self
.Bind(wx
.EVT_MENU
,
235 id = self
.popup_id_edit
)
236 self
.Bind(wx
.EVT_MENU
,
238 id = self
.popup_id_message
)
239 self
.Bind(wx
.EVT_MENU
,
241 id = self
.popup_id_contact
)
242 self
.Bind(wx
.EVT_MENU
,
244 id = self
.popup_id_call
)
245 self
.Bind(wx
.EVT_MENU
,
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
,
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
)
277 self
.InsertColumn(i
, self
.columns
[i
])
279 # resize columns to fit content
281 # FIXME: this should be acquired better!
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
:
293 size
= self
.GetTextExtent(StrConv(current
[self
.keys
[i
]]))
294 if (size
[0] > maxval
[i
]):
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).
306 self
.values
= self
.allvalues
312 match
= re
.compile('.*%s.*' % re
.escape(text
), re
.I
)
313 elif filter_type
== 1:
315 match
= re
.compile(text
, re
.I
)
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
)
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
))
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
)
356 def ShowRow(self
, index
):
360 if (self
.GetItemCount() > index
362 and self
.GetCountPerPage() > 0):
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
)
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.
381 self
.cfg
.Write('/BrowserSortKey/%s' % self
.type,
383 self
.cfg
.WriteInt('/BrowserSortOrder/%s' % self
.type,
387 self
.allvalues
= values
391 self
.SetItemCount(len(values
))
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
:
400 self
.sortkey
= readsort
406 def Resort(self
, col
):
408 Changes sort order of listing.
412 item
= self
.values
[self
.itemno
]
415 # find keys and order
416 nextsort
= self
.keys
[col
]
417 if nextsort
== self
.sortkey
:
418 self
.sortorder
= -1 * self
.sortorder
421 self
.sortkey
= nextsort
424 self
.values
.sort(self
.Sorter
)
427 for i
in range(self
.GetColumnCount()):
428 self
.ClearColumnImage(i
)
429 if self
.sortorder
== 1:
430 image
= self
.downarrow
433 self
.SetColumnImage(col
, image
)
437 self
.ShowRow(self
.values
.index(item
))
439 def RefreshView(self
):
441 Refresh displayed items.
443 if self
.GetItemCount() != 0:
444 top
= self
.GetTopItem()
447 count
= self
.GetCountPerPage()
448 totalcount
= self
.GetItemCount()
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]:
465 def DoSelectedDelete(self
):
467 Delete selected message.
470 index
= self
.GetFirstSelected()
472 lst
.append(self
.values
[index
])
473 index
= self
.GetNextSelected(index
)
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
)
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':
504 self
.popup_index
= evt
.m_itemIndex
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
]
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
):
591 index
= self
.GetFirstSelected()
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
):
620 if item
>= len(self
.values
):
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
629 if self
.values
[item
]['Synced']: