| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed list controls and widgets.
2
3 TODO:
4
5 From: Rob McMullen <rob.mcmullen@gmail.com>
6 To: wxPython-users@lists.wxwidgets.org
7 Subject: Re: [wxPython-users] ANN: ColumnSizer mixin for ListCtrl
8
9 Thanks for all the suggestions, on and off line. There's an update
10 with a new name (ColumnAutoSizeMixin) and better sizing algorithm at:
11
12 http://trac.flipturn.org/browser/trunk/peppy/lib/column_autosize.py
13
14 sorting: http://code.activestate.com/recipes/426407/
15 """
16 #================================================================
17 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
18 __license__ = "GPL v2 or later"
19
20
21 import sys
22 import types
23 import logging
24 import thread
25 import time
26 import re as regex
27
28
29 import wx
30 import wx.lib.mixins.listctrl as listmixins
31
32
33 _log = logging.getLogger('gm.list_ui')
34 #================================================================
35 # FIXME: configurable callback on double-click action
36
37 -def get_choices_from_list (
38 parent=None,
39 msg=None,
40 caption=None,
41 columns=None,
42 choices=None,
43 data=None,
44 selections=None,
45 edit_callback=None,
46 new_callback=None,
47 delete_callback=None,
48 refresh_callback=None,
49 single_selection=False,
50 can_return_empty=False,
51 ignore_OK_button=False,
52 left_extra_button=None,
53 middle_extra_button=None,
54 right_extra_button=None,
55 list_tooltip_callback=None):
56 """Let user select item(s) from a list.
57
58 - new_callback: ()
59 - edit_callback: (item data)
60 - delete_callback: (item data)
61 - refresh_callback: (listctrl)
62 - list_tooltip_callback: (item data)
63
64 - left/middle/right_extra_button: (label, tooltip, <callback>)
65 <callback> is called with item_data as the only argument
66
67 returns:
68 on [CANCEL]: None
69 on [OK]:
70 if any items selected:
71 list of selected items
72 else:
73 if can_return_empty is True:
74 empty list
75 else:
76 None
77 """
78 if caption is None:
79 caption = _('generic multi choice dialog')
80
81 if single_selection:
82 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
83 else:
84 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
85
86 dlg.refresh_callback = refresh_callback
87 dlg.edit_callback = edit_callback
88 dlg.new_callback = new_callback
89 dlg.delete_callback = delete_callback
90 dlg.list_tooltip_callback = list_tooltip_callback
91
92 dlg.ignore_OK_button = ignore_OK_button
93 dlg.left_extra_button = left_extra_button
94 dlg.middle_extra_button = middle_extra_button
95 dlg.right_extra_button = right_extra_button
96
97 dlg.set_columns(columns = columns)
98
99 if refresh_callback is None:
100 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible
101 dlg.set_column_widths()
102
103 if data is not None:
104 dlg.set_data(data = data) # can override data set if refresh_callback is not None
105
106 if selections is not None:
107 dlg.set_selections(selections = selections)
108 dlg.can_return_empty = can_return_empty
109
110 btn_pressed = dlg.ShowModal()
111 sels = dlg.get_selected_item_data(only_one = single_selection)
112 dlg.Destroy()
113
114 if btn_pressed == wx.ID_OK:
115 if can_return_empty and (sels is None):
116 return []
117 return sels
118
119 return None
120 #----------------------------------------------------------------
121 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
122
124 """A dialog holding a list and a few buttons to act on the items."""
125
126 # FIXME: configurable callback on double-click action
127
129
130 try:
131 msg = kwargs['msg']
132 del kwargs['msg']
133 except KeyError: msg = None
134
135 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
136
137 self.message = msg
138
139 self.left_extra_button = None
140 self.middle_extra_button = None
141 self.right_extra_button = None
142
143 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
144 self.new_callback = None # called when NEW button pressed, no argument passed in
145 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
146 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
147
148 self.can_return_empty = False
149 self.ignore_OK_button = False # by default do show/use the OK button
150 #------------------------------------------------------------
152 self._LCTRL_items.set_columns(columns = columns)
153 #------------------------------------------------------------
155 self._LCTRL_items.set_column_widths(widths = widths)
156 #------------------------------------------------------------
158 self._LCTRL_items.set_string_items(items = items)
159 self._LCTRL_items.set_column_widths()
160 self._LCTRL_items.Select(0)
161 #------------------------------------------------------------
163 self._LCTRL_items.set_selections(selections = selections)
164 if selections is None:
165 return
166 if len(selections) == 0:
167 return
168 if self.ignore_OK_button:
169 return
170 self._BTN_ok.Enable(True)
171 self._BTN_ok.SetDefault()
172 #------------------------------------------------------------
175 #------------------------------------------------------------
177 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
178 #------------------------------------------------------------
179 # event handlers
180 #------------------------------------------------------------
182 if not self.__ignore_OK_button:
183 self._BTN_ok.SetDefault()
184 self._BTN_ok.Enable(True)
185
186 if self.edit_callback is not None:
187 self._BTN_edit.Enable(True)
188
189 if self.delete_callback is not None:
190 self._BTN_delete.Enable(True)
191 #------------------------------------------------------------
193 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
194 if not self.can_return_empty:
195 self._BTN_cancel.SetDefault()
196 self._BTN_ok.Enable(False)
197 self._BTN_edit.Enable(False)
198 self._BTN_delete.Enable(False)
199 #------------------------------------------------------------
214 #------------------------------------------------------------
231 #------------------------------------------------------------
252 #------------------------------------------------------------
268 #------------------------------------------------------------
284 #------------------------------------------------------------
300 #------------------------------------------------------------
301 # properties
302 #------------------------------------------------------------
316
317 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
318 #------------------------------------------------------------
334
335 left_extra_button = property(lambda x:x, _set_left_extra_button)
336 #------------------------------------------------------------
352
353 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
354 #------------------------------------------------------------
370
371 right_extra_button = property(lambda x:x, _set_right_extra_button)
372 #------------------------------------------------------------
375
377 if callback is not None:
378 if self.refresh_callback is None:
379 raise ValueError('refresh callback must be set before new callback can be set')
380 if not callable(callback):
381 raise ValueError('<new> callback is not a callable: %s' % callback)
382 self.__new_callback = callback
383
384 if callback is None:
385 self._BTN_new.Enable(False)
386 self._BTN_new.Hide()
387 else:
388 self._BTN_new.Enable(True)
389 self._BTN_new.Show()
390
391 new_callback = property(_get_new_callback, _set_new_callback)
392 #------------------------------------------------------------
395
397 if callback is not None:
398 if not callable(callback):
399 raise ValueError('<edit> callback is not a callable: %s' % callback)
400 self.__edit_callback = callback
401
402 if callback is None:
403 self._BTN_edit.Enable(False)
404 self._BTN_edit.Hide()
405 else:
406 self._BTN_edit.Enable(True)
407 self._BTN_edit.Show()
408
409 edit_callback = property(_get_edit_callback, _set_edit_callback)
410 #------------------------------------------------------------
413
415 if callback is not None:
416 if self.refresh_callback is None:
417 raise ValueError('refresh callback must be set before delete callback can be set')
418 if not callable(callback):
419 raise ValueError('<delete> callback is not a callable: %s' % callback)
420 self.__delete_callback = callback
421
422 if callback is None:
423 self._BTN_delete.Enable(False)
424 self._BTN_delete.Hide()
425 else:
426 self._BTN_delete.Enable(True)
427 self._BTN_delete.Show()
428
429 delete_callback = property(_get_delete_callback, _set_delete_callback)
430 #------------------------------------------------------------
433
435 wx.BeginBusyCursor()
436 try:
437 self.refresh_callback(lctrl = self._LCTRL_items)
438 finally:
439 wx.EndBusyCursor()
440 self._LCTRL_items.set_column_widths()
441
443 if callback is not None:
444 if not callable(callback):
445 raise ValueError('<refresh> callback is not a callable: %s' % callback)
446 self.__refresh_callback = callback
447 if callback is not None:
448 wx.CallAfter(self._set_refresh_callback_helper)
449
450 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
451 #------------------------------------------------------------
453 self._LCTRL_items.item_tooltip_callback = callback
454
455 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
456 #def _get_tooltip(self, item): # inside a class
457 #def _get_tooltip(item): # outside a class
458 #------------------------------------------------------------
460 if message is None:
461 self._LBL_message.Hide()
462 return
463 self._LBL_message.SetLabel(message)
464 self._LBL_message.Show()
465
466 message = property(lambda x:x, _set_message)
467 #================================================================
468 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
469
471 """A panel holding a generic multi-column list and action buttions."""
472
474
475 try:
476 msg = kwargs['msg']
477 del kwargs['msg']
478 except KeyError: msg = None
479
480 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
481
482 if msg is None:
483 self._LBL_message.Hide()
484 else:
485 self._LBL_message.SetLabel(msg)
486
487 # new/edit/delete must return True/False to enable refresh
488 self.__new_callback = None # called when NEW button pressed, no argument passed in
489 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
490 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
491 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
492
493 self.__select_callback = None # called when an item is selected, data of topmost selected item passed in
494
495 self.left_extra_button = None
496 self.middle_extra_button = None
497 self.right_extra_button = None
498 #------------------------------------------------------------
499 # external API
500 #------------------------------------------------------------
502 self._LCTRL_items.set_columns(columns = columns)
503 #------------------------------------------------------------
505 self._LCTRL_items.set_string_items(items = items)
506 self._LCTRL_items.set_column_widths()
507
508 if (items is None) or (len(items) == 0):
509 self._BTN_edit.Enable(False)
510 self._BTN_remove.Enable(False)
511 else:
512 self._LCTRL_items.Select(0)
513 #------------------------------------------------------------
516 #------------------------------------------------------------
519 #------------------------------------------------------------
521 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
522 #------------------------------------------------------------
523 # event handlers
524 #------------------------------------------------------------
526 if self.edit_callback is not None:
527 self._BTN_edit.Enable(True)
528 if self.delete_callback is not None:
529 self._BTN_remove.Enable(True)
530 if self.__select_callback is not None:
531 item = self._LCTRL_items.get_selected_item_data(only_one=True)
532 self.__select_callback(item)
533 #------------------------------------------------------------
535 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
536 self._BTN_edit.Enable(False)
537 self._BTN_remove.Enable(False)
538 if self.__select_callback is not None:
539 self.__select_callback(None)
540 #------------------------------------------------------------
551 #------------------------------------------------------------
556 #------------------------------------------------------------
570 #------------------------------------------------------------
584 #------------------------------------------------------------
600 #------------------------------------------------------------
616 #------------------------------------------------------------
632 #------------------------------------------------------------
633 # properties
634 #------------------------------------------------------------
637
639 if callback is not None:
640 if not callable(callback):
641 raise ValueError('<new> callback is not a callable: %s' % callback)
642 self.__new_callback = callback
643 self._BTN_add.Enable(callback is not None)
644
645 new_callback = property(_get_new_callback, _set_new_callback)
646 #------------------------------------------------------------
649
651 if callback is not None:
652 if not callable(callback):
653 raise ValueError('<select> callback is not a callable: %s' % callback)
654 self.__select_callback = callback
655
656 select_callback = property(_get_select_callback, _set_select_callback)
657 #------------------------------------------------------------
660
662 if msg is None:
663 self._LBL_message.Hide()
664 self._LBL_message.SetLabel(u'')
665 else:
666 self._LBL_message.SetLabel(msg)
667 self._LBL_message.Show()
668 self.Layout()
669
670 message = property(_get_message, _set_message)
671 #------------------------------------------------------------
687
688 left_extra_button = property(lambda x:x, _set_left_extra_button)
689 #------------------------------------------------------------
705
706 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
707 #------------------------------------------------------------
723
724 right_extra_button = property(lambda x:x, _set_right_extra_button)
725 #================================================================
726 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
727
729
731
732 try:
733 msg = kwargs['msg']
734 del kwargs['msg']
735 except KeyError:
736 msg = None
737
738 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
739
740 if msg is None:
741 self._LBL_msg.Hide()
742 else:
743 self._LBL_msg.SetLabel(msg)
744
745 self._LCTRL_left.activate_callback = self.__pick_selected
746 #self._LCTRL_left.item_tooltip_callback = self.__on_get_item_tooltip
747 self.__extra_button_callback = None
748
749 self._LCTRL_left.SetFocus()
750 #------------------------------------------------------------
751 # external API
752 #------------------------------------------------------------
754 self._LCTRL_left.set_columns(columns = columns)
755 if columns_right is None:
756 self._LCTRL_right.set_columns(columns = columns)
757 else:
758 if len(columns_right) < len(columns):
759 cols = columns
760 else:
761 cols = columns_right[:len(columns)]
762 self._LCTRL_right.set_columns(columns = cols)
763 #------------------------------------------------------------
765 self._LCTRL_left.set_string_items(items = items)
766 self._LCTRL_left.set_column_widths()
767 self._LCTRL_right.set_string_items()
768
769 self._BTN_left2right.Enable(False)
770 self._BTN_right2left.Enable(False)
771 #------------------------------------------------------------
774 #------------------------------------------------------------
779 #------------------------------------------------------------
781 self._LCTRL_right.set_string_items(picks)
782 self._LCTRL_right.set_column_widths()
783 if data is not None:
784 self._LCTRL_right.set_data(data = data)
785 #------------------------------------------------------------
788 #------------------------------------------------------------
790 return self._LCTRL_right.get_item_data()
791
792 picks = property(get_picks, lambda x:x)
793 #------------------------------------------------------------
809
810 extra_button = property(lambda x:x, _set_extra_button)
811 #------------------------------------------------------------
812 # internal helpers
813 #------------------------------------------------------------
815 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
816 return
817
818 right_items = self._LCTRL_right.get_string_items()
819 right_data = self._LCTRL_right.get_item_data()
820
821 right_items.extend(self._LCTRL_left.get_selected_string_items(only_one = False))
822 self._LCTRL_right.set_string_items(items = right_items)
823 del right_items
824
825 if right_data is None:
826 self._LCTRL_right.set_data(data = self._LCTRL_left.get_selected_item_data(only_one = False))
827 else:
828 right_data.extend(self._LCTRL_left.get_selected_item_data(only_one = False))
829 self._LCTRL_right.set_data(data = right_data)
830 del right_data
831
832 self._LCTRL_right.set_column_widths()
833 #------------------------------------------------------------
835 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
836 return
837
838 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
839 self._LCTRL_right.remove_item(item_idx)
840
841 if self._LCTRL_right.GetItemCount() == 0:
842 self._BTN_right2left.Enable(False)
843 #------------------------------------------------------------
844 # event handlers
845 #------------------------------------------------------------
848 #------------------------------------------------------------
850 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
851 self._BTN_left2right.Enable(False)
852 #------------------------------------------------------------
855 #------------------------------------------------------------
857 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
858 self._BTN_right2left.Enable(False)
859 #------------------------------------------------------------
862 #------------------------------------------------------------
865 #------------------------------------------------------------
868 # item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
869 # if not self.__left_extra_button_callback(item_data):
870 # self._LCTRL_items.SetFocus()
871 # return
872 # if self.refresh_callback is None:
873 # self._LCTRL_items.SetFocus()
874 # return
875 # wx.BeginBusyCursor()
876 # try:
877 # self.refresh_callback(lctrl = self._LCTRL_items)
878 # finally:
879 # wx.EndBusyCursor()
880 # self._LCTRL_items.set_column_widths()
881 # self._LCTRL_items.SetFocus()
882 #================================================================
883 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin):
884
885 get_data_idx_for_item = wx.ListCtrl.GetItemData
886
887 sort_order_tags = {
888 True: u' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
889 False: u' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
890 }
891
893
894 self.debug = None
895
896 try:
897 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
898 except KeyError:
899 kwargs['style'] = wx.LC_REPORT
900
901 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
902
903 wx.ListCtrl.__init__(self, *args, **kwargs)
904 listmixins.ListCtrlAutoWidthMixin.__init__(self)
905
906 # required for column sorting, MUST have this name
907 self._invalidate_item2data_idx_map() # must be called after each (external/direct) list item update
908 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?)
909 # for debugging sorting:
910 #self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
911
912 self.__widths = None
913 self.__data = None
914 self.__activate_callback = None
915 self.__rightclick_callback = None
916
917 self.__item_tooltip_callback = None
918 self.__tt_last_item = None
919 self.__tt_static_part = _("""Select the items you want to work on.
920
921 A discontinuous selection may depend on your holding down a platform-dependent modifier key (<ctrl>, <alt>, etc) or key combination (eg. <ctrl-shift> or <ctrl-alt>) while clicking.""")
922 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
923
924 self.__next_line_to_search = 0
925 self.__search_data = None
926 self.__search_dlg = None
927 self.__searchable_cols = None
928 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
929 self.Bind(wx.EVT_CHAR, self._on_char)
930 self.Bind(wx.EVT_FIND_CLOSE, self._on_search_dlg_closed)
931 self.Bind(wx.EVT_FIND, self._on_search_first_match)
932 self.Bind(wx.EVT_FIND_NEXT, self._on_search_next_match)
933 #------------------------------------------------------------
934 # setters
935 #------------------------------------------------------------
937 """(Re)define the columns.
938
939 Note that this will (have to) delete the items.
940 """
941 self.ClearAll()
942 self.__tt_last_item = None
943 if columns is None:
944 return
945 for idx in range(len(columns)):
946 self.InsertColumn(idx, columns[idx])
947
948 self._invalidate_item2data_idx_map()
949 #------------------------------------------------------------
951 """Set the column width policy.
952
953 widths = None:
954 use previous policy if any or default policy
955 widths != None:
956 use this policy and remember it for later calls
957
958 This means there is no way to *revert* to the default policy :-(
959 """
960 # explicit policy ?
961 if widths is not None:
962 self.__widths = widths
963 for idx in range(len(self.__widths)):
964 self.SetColumnWidth(col = idx, width = self.__widths[idx])
965 return
966
967 # previous policy ?
968 if self.__widths is not None:
969 for idx in range(len(self.__widths)):
970 self.SetColumnWidth(col = idx, width = self.__widths[idx])
971 return
972
973 # default policy !
974 if self.GetItemCount() == 0:
975 width_type = wx.LIST_AUTOSIZE_USEHEADER
976 else:
977 width_type = wx.LIST_AUTOSIZE
978 for idx in range(self.GetColumnCount()):
979 self.SetColumnWidth(col = idx, width = width_type)
980 #------------------------------------------------------------
982 """All item members must be unicode()able or None."""
983
984 wx.BeginBusyCursor()
985
986 loop = 0
987 while True:
988 if loop > 3:
989 _log.debug('unable to delete list items after looping 3 times, continuing and hoping for the best')
990 break
991 loop += 1
992 if self.debug is not None:
993 _log.debug('[round %s] GetItemCount() before DeleteAllItems(): %s (%s, thread [%s])', loop, self.GetItemCount(), self.debug, thread.get_ident())
994 if not self.DeleteAllItems():
995 _log.debug('DeleteAllItems() failed (%s)', self.debug)
996 item_count = self.GetItemCount()
997 if self.debug is not None:
998 _log.debug('GetItemCount() after DeleteAllItems(): %s (%s)', item_count, self.debug)
999 if item_count == 0:
1000 break
1001 wx.SafeYield(None, True)
1002 _log.debug('GetItemCount() not 0 after DeleteAllItems() (%s)', self.debug)
1003 time.sleep(0.3)
1004 wx.SafeYield(None, True)
1005
1006 if items is None:
1007 self.data = None
1008 wx.EndBusyCursor()
1009 return
1010
1011 for item in items:
1012 try:
1013 item[0]
1014 if not isinstance(item, basestring):
1015 is_numerically_iterable = True
1016 # do not iterate over individual chars in a string, however
1017 else:
1018 is_numerically_iterable = False
1019 except TypeError:
1020 is_numerically_iterable = False
1021
1022 if is_numerically_iterable:
1023 # cannot use errors='replace' since then
1024 # None/ints/unicode strings fail to get encoded
1025 col_val = unicode(item[0])
1026 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1027 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1028 col_val = unicode(item[col_num])
1029 self.SetStringItem(index = row_num, col = col_num, label = col_val)
1030 else:
1031 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded
1032 col_val = unicode(item)
1033 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1034 # item data must not be null for sorting to work, BTW, it is useful
1035 self.SetItemData(row_num, row_num)
1036
1037 self.data = items
1038 self._invalidate_item2data_idx_map()
1039
1040 wx.EndBusyCursor()
1041 #------------------------------------------------------------
1043 """<data> assumed to be a list corresponding to the item indices"""
1044 # this is hard to enforce
1045 # FIXME: data should be added together with string items
1046 if data is not None:
1047 item_count = self.GetItemCount()
1048 if len(data) != item_count:
1049 _log.debug('<data> length (%s) must be equal to number of list items (%s) (%s, thread [%s])', len(data), item_count, self.debug, thread.get_ident())
1050 # update item idx <-> item data map, anyway (?)
1051 for item_idx in range(len(data)):
1052 self.SetItemData(item_idx, item_idx)
1053 self.__data = data
1054 self.__tt_last_item = None
1055 # string data (rows/visible list items) not modified,
1056 # so no need to call _update_item2data_idx_map
1057 return
1058
1060 # slower than "return self.__data" but helps with detecting
1061 # problems with len(__data)<>self.GetItemCount()
1062 return self.get_item_data() # item_idx is None: returns all data
1063
1064 data = property(_get_data, set_data)
1065 #------------------------------------------------------------
1067 self.Select(0, on = 0)
1068 if selections is None:
1069 return
1070 for idx in selections:
1071 self.Select(idx = idx, on = 1)
1072 #self.SetItemState(idx, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
1073
1075 if self.__is_single_selection:
1076 return [self.GetFirstSelected()]
1077 selections = []
1078 idx = self.GetFirstSelected()
1079 while idx != -1:
1080 selections.append(idx)
1081 idx = self.GetNextSelected(idx)
1082 return selections
1083
1084 selections = property(__get_selections, set_selections)
1085 #------------------------------------------------------------
1086 # getters
1087 #------------------------------------------------------------
1089 labels = []
1090 for col_idx in self.GetColumnCount():
1091 col = self.GetColumn(col = col_idx)
1092 labels.append(col.GetText())
1093 return labels
1094 #------------------------------------------------------------
1098 #------------------------------------------------------------
1101 #------------------------------------------------------------
1104 #------------------------------------------------------------
1106
1107 if self.__is_single_selection or only_one:
1108 return self.GetFirstSelected()
1109
1110 items = []
1111 idx = self.GetFirstSelected()
1112 while idx != -1:
1113 items.append(idx)
1114 idx = self.GetNextSelected(idx)
1115
1116 return items
1117 #------------------------------------------------------------
1119
1120 if self.__is_single_selection or only_one:
1121 return self.GetItemText(self.GetFirstSelected())
1122
1123 items = []
1124 idx = self.GetFirstSelected()
1125 while idx != -1:
1126 items.append(self.GetItemText(idx))
1127 idx = self.GetNextSelected(idx)
1128
1129 return items
1130 #------------------------------------------------------------
1132 if self.__data is None: # this isn't entirely clean
1133 return None
1134 # proper index mapping string items to data is stored as item data
1135 # this enables changing of items order, and still returning proper data
1136 if item_idx is not None:
1137 return self.__data[self.get_data_idx_for_item(item_idx)]
1138
1139 # if <idx> is None return all data up to item_count,
1140 # in case of len(__data) <> self.GetItemCount() this
1141 # gives the chance to figure out what is going on
1142 return [ self.__data[self.get_data_idx_for_item(item_idx)] for item_idx in range(self.GetItemCount()) ]
1143 #------------------------------------------------------------
1145
1146 if self.__is_single_selection or only_one:
1147 if self.__data is None:
1148 return None
1149 idx = self.GetFirstSelected()
1150 if idx == -1:
1151 return None
1152 return self.__data[self.get_data_idx_for_item(idx)]
1153
1154 data = []
1155 if self.__data is None:
1156 return data
1157 idx = self.GetFirstSelected()
1158 while idx != -1:
1159 data.append(self.__data[self.get_data_idx_for_item(idx)])
1160 idx = self.GetNextSelected(idx)
1161
1162 return data
1163 #------------------------------------------------------------
1165 self.Select(idx = self.GetFirstSelected(), on = 0)
1166 #------------------------------------------------------------
1168 if self.__data is not None:
1169 del self.__data[self.get_data_idx_for_item(item_idx)]
1170 self.DeleteItem(item_idx)
1171 self.__tt_last_item = None
1172 #------------------------------------------------------------
1173 # event handlers
1174 #------------------------------------------------------------
1176 event.Skip()
1177 if self.__activate_callback is not None:
1178 self.__activate_callback(event)
1179 #------------------------------------------------------------
1181 event.Skip()
1182 if self.__rightclick_callback is not None:
1183 self.__rightclick_callback(event)
1184 #------------------------------------------------------------
1186
1187 if evt.GetModifiers() != wx.MOD_CMD:
1188 evt.Skip()
1189 return
1190
1191 if unichr(evt.GetRawKeyCode()) != u'f':
1192 evt.Skip()
1193 return
1194
1195 if self.__search_dlg is not None:
1196 self.__search_dlg.Close()
1197 return
1198
1199 if len(self.__searchable_cols) == 0:
1200 return
1201
1202 if self.__search_data is None:
1203 self.__search_data = wx.FindReplaceData()
1204 self.__search_dlg = wx.FindReplaceDialog (
1205 self,
1206 self.__search_data,
1207 _('Search in list'),
1208 wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD
1209 )
1210 self.__search_dlg.Show(True)
1211 #------------------------------------------------------------
1215 #------------------------------------------------------------
1217 evt.Skip()
1218 if self.__search_dlg is None:
1219 return
1220 print self.FindFocus()
1221 print self.__search_dlg
1222 #self.__search_dlg.Close()
1223 #------------------------------------------------------------
1225 for row_idx in range(self.__next_line_to_search, self.ItemCount):
1226 for col_idx in range(self.ColumnCount):
1227 if col_idx not in self.__searchable_cols:
1228 continue
1229 col_val = self.GetItem(row_idx, col_idx).GetText()
1230 if regex.search(search_term, col_val, regex.U | regex.I) is not None:
1231 self.Select(row_idx)
1232 self.EnsureVisible(row_idx)
1233 if row_idx == self.ItemCount - 1:
1234 # wrap around
1235 self.__next_line_to_search = 0
1236 else:
1237 self.__next_line_to_search = row_idx + 1
1238 return True
1239 # wrap around
1240 self.__next_line_to_search = 0
1241 return False
1242 #------------------------------------------------------------
1245 #------------------------------------------------------------
1248 #------------------------------------------------------------
1250 """Update tooltip on mouse motion.
1251
1252 for s in dir(wx):
1253 if s.startswith('LIST_HITTEST'):
1254 print s, getattr(wx, s)
1255
1256 LIST_HITTEST_ABOVE 1
1257 LIST_HITTEST_BELOW 2
1258 LIST_HITTEST_NOWHERE 4
1259 LIST_HITTEST_ONITEM 672
1260 LIST_HITTEST_ONITEMICON 32
1261 LIST_HITTEST_ONITEMLABEL 128
1262 LIST_HITTEST_ONITEMRIGHT 256
1263 LIST_HITTEST_ONITEMSTATEICON 512
1264 LIST_HITTEST_TOLEFT 1024
1265 LIST_HITTEST_TORIGHT 2048
1266 """
1267 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
1268
1269 # pointer on item related area at all ?
1270 if where_flag not in [
1271 wx.LIST_HITTEST_ONITEMLABEL,
1272 wx.LIST_HITTEST_ONITEMICON,
1273 wx.LIST_HITTEST_ONITEMSTATEICON,
1274 wx.LIST_HITTEST_ONITEMRIGHT,
1275 wx.LIST_HITTEST_ONITEM
1276 ]:
1277 self.__tt_last_item = None # not on any item
1278 self.SetToolTipString(self.__tt_static_part)
1279 return
1280
1281 # same item as last time around ?
1282 if self.__tt_last_item == item_idx:
1283 return
1284
1285 # remeber the new item we are on
1286 self.__tt_last_item = item_idx
1287
1288 # HitTest() can return -1 if it so pleases, meaning that no item
1289 # was hit or else that maybe there aren't any items (empty list)
1290 if item_idx == wx.NOT_FOUND:
1291 self.SetToolTipString(self.__tt_static_part)
1292 return
1293
1294 # do we *have* item data ?
1295 if self.__data is None:
1296 self.SetToolTipString(self.__tt_static_part)
1297 return
1298
1299 # under some circumstances the item_idx returned
1300 # by HitTest() may be out of bounds with respect to
1301 # self.__data, this hints at a sync problem between
1302 # setting display items and associated data
1303 if (
1304 (item_idx > (len(self.__data) - 1))
1305 or
1306 (item_idx < -1)
1307 ):
1308 self.SetToolTipString(self.__tt_static_part)
1309 print "*************************************************************"
1310 print "GNUmed has detected an inconsistency with list item tooltips."
1311 print ""
1312 print "This is not a big problem and you can keep working."
1313 print ""
1314 print "However, please send us the following so we can fix GNUmed:"
1315 print ""
1316 print "item idx: %s" % item_idx
1317 print 'where flag: %s' % where_flag
1318 print 'data list length: %s' % len(self.__data)
1319 print "*************************************************************"
1320 return
1321
1322 dyna_tt = None
1323 if self.__item_tooltip_callback is not None:
1324 dyna_tt = self.__item_tooltip_callback(self.__data[self.get_data_idx_for_item(item_idx)])
1325
1326 if dyna_tt is None:
1327 self.SetToolTipString(self.__tt_static_part)
1328 return
1329
1330 self.SetToolTipString(dyna_tt)
1331 #------------------------------------------------------------
1332 # properties
1333 #------------------------------------------------------------
1336
1338 if callback is None:
1339 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1340 else:
1341 if not callable(callback):
1342 raise ValueError('<activate> callback is not a callable: %s' % callback)
1343 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1344 self.__activate_callback = callback
1345
1346 activate_callback = property(_get_activate_callback, _set_activate_callback)
1347 #------------------------------------------------------------
1350
1352 if callback is None:
1353 self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK)
1354 else:
1355 if not callable(callback):
1356 raise ValueError('<rightclick> callback is not a callable: %s' % callback)
1357 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
1358 self.__rightclick_callback = callback
1359
1360 rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback)
1361 #------------------------------------------------------------
1363 if callback is not None:
1364 if not callable(callback):
1365 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
1366 self.__item_tooltip_callback = callback
1367
1368 # the callback must be a function which takes a single argument
1369 # the argument is the data for the item the tooltip is on
1370 # the callback must return None if no item tooltip is to be shown
1371 # otherwise it must return a string (possibly with \n)
1372 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1373 #------------------------------------------------------------
1375 # zero-based list of which columns to search
1376 if cols is None:
1377 self.__searchable_cols = range(self.ColumnCount)
1378 return
1379 # weed out columns to be searched which
1380 # don't exist an uniquify them
1381 new_cols = {}
1382 for col in cols:
1383 if col < self.ColumnCount:
1384 new_cols[col] = True
1385 self.__searchable_cols = new_cols.keys()
1386
1387 searchable_columns = property(lambda x:x, _set_searchable_cols)
1388 #------------------------------------------------------------
1389 # ColumnSorterMixin API
1390 #------------------------------------------------------------
1392 if self.itemDataMap is None:
1393 self._update_item2data_idx_map()
1394 # required
1395 return self
1396 #------------------------------------------------------------
1398 self._cleanup_column_headers()
1399 # mark sort column
1400 col_idx, is_ascending = self.GetSortState()
1401 col_state = self.GetColumn(col_idx)
1402 col_state.m_text += self.sort_order_tags[is_ascending]
1403 self.SetColumn(col_idx, col_state)
1404 #------------------------------------------------------------
1406 dict2sort = {}
1407 item_count = self.GetItemCount()
1408 if item_count == 0:
1409 return dict2sort
1410 col_count = self.GetColumnCount()
1411 for item_idx in range(item_count):
1412 dict2sort[item_idx] = ()
1413 if col_count == 0:
1414 continue
1415 for col_idx in range(col_count):
1416 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
1417
1418 return dict2sort
1419 #------------------------------------------------------------
1421 for col_idx in range(self.ColumnCount):
1422 col_state = self.GetColumn(col_idx)
1423 if col_state.m_text.endswith(self.sort_order_tags[True]):
1424 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[True])]
1425 if col_state.m_text.endswith(self.sort_order_tags[False]):
1426 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[False])]
1427 self.SetColumn(col_idx, col_state)
1428 #------------------------------------------------------------
1430 self.itemDataMap = None
1431 self.SetColumnCount(self.GetColumnCount())
1432 self._cleanup_column_headers()
1433 #------------------------------------------------------------
1436 #------------------------------------------------------------
1438 # for debugging:
1439 # print "column clicked : %s" % (event.GetColumn())
1440 # column, order = self.GetSortState()
1441 # print "column %s sort %s" % (column, order)
1442 # print self._colSortFlag
1443 # print self.itemDataMap
1444 event.Skip()
1445
1446 #================================================================
1447 # main
1448 #----------------------------------------------------------------
1449 if __name__ == '__main__':
1450
1451 if len(sys.argv) < 2:
1452 sys.exit()
1453
1454 if sys.argv[1] != 'test':
1455 sys.exit()
1456
1457 sys.path.insert(0, '../../')
1458
1459 from Gnumed.pycommon import gmI18N
1460 gmI18N.activate_locale()
1461 gmI18N.install_domain()
1462
1463 #------------------------------------------------------------
1465 app = wx.PyWidgetTester(size = (400, 500))
1466 dlg = wx.MultiChoiceDialog (
1467 parent = None,
1468 message = 'test message',
1469 caption = 'test caption',
1470 choices = ['a', 'b', 'c', 'd', 'e']
1471 )
1472 dlg.ShowModal()
1473 sels = dlg.GetSelections()
1474 print "selected:"
1475 for sel in sels:
1476 print sel
1477 #------------------------------------------------------------
1483
1484 def refresh(lctrl):
1485 choices = ['a', 'b', 'c']
1486 lctrl.set_string_items(choices)
1487
1488 app = wx.PyWidgetTester(size = (200, 50))
1489 chosen = get_choices_from_list (
1490 # msg = 'select a health issue\nfrom the list below\n',
1491 caption = 'select health issues',
1492 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
1493 #columns = ['issue', 'no of episodes']
1494 columns = ['issue'],
1495 refresh_callback = refresh
1496 #, edit_callback = edit
1497 )
1498 print "chosen:"
1499 print chosen
1500 #------------------------------------------------------------
1502 app = wx.PyWidgetTester(size = (200, 50))
1503 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1504 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1505 #dlg.set_columns(['Plugins'], [])
1506 dlg.set_string_items(['patient', 'emr', 'docs'])
1507 result = dlg.ShowModal()
1508 print result
1509 print dlg.get_picks()
1510 #------------------------------------------------------------
1511 #test_get_choices_from_list()
1512 #test_wxMultiChoiceDialog()
1513 test_item_picker_dlg()
1514
1515 #================================================================
1516 #
1517
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 10 03:56:30 2013 | http://epydoc.sourceforge.net |