| 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> [, wants_list_ctrl])
65 wants_list_ctrl is optional
66 <callback> is called with item_data (or listctrl) as the only argument
67
68 returns:
69 on [CANCEL]: None
70 on [OK]:
71 if any items selected:
72 list of selected items
73 else:
74 if can_return_empty is True:
75 empty list
76 else:
77 None
78 """
79 if caption is None:
80 caption = _('generic multi choice dialog')
81
82 if single_selection:
83 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
84 else:
85 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
86
87 dlg.refresh_callback = refresh_callback
88 dlg.edit_callback = edit_callback
89 dlg.new_callback = new_callback
90 dlg.delete_callback = delete_callback
91 dlg.list_tooltip_callback = list_tooltip_callback
92
93 dlg.ignore_OK_button = ignore_OK_button
94 dlg.left_extra_button = left_extra_button
95 dlg.middle_extra_button = middle_extra_button
96 dlg.right_extra_button = right_extra_button
97
98 dlg.set_columns(columns = columns)
99
100 if refresh_callback is None:
101 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible
102 dlg.set_column_widths()
103
104 if data is not None:
105 dlg.set_data(data = data) # can override data set if refresh_callback is not None
106
107 if selections is not None:
108 dlg.set_selections(selections = selections)
109 dlg.can_return_empty = can_return_empty
110
111 btn_pressed = dlg.ShowModal()
112 sels = dlg.get_selected_item_data(only_one = single_selection)
113 dlg.Destroy()
114
115 if btn_pressed == wx.ID_OK:
116 if can_return_empty and (sels is None):
117 return []
118 return sels
119
120 return None
121 #----------------------------------------------------------------
122 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
123
125 """A dialog holding a list and a few buttons to act on the items."""
126
127 # FIXME: configurable callback on double-click action
128
130
131 try:
132 msg = kwargs['msg']
133 del kwargs['msg']
134 except KeyError: msg = None
135
136 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
137
138 self.message = msg
139
140 self.left_extra_button = None
141 self.middle_extra_button = None
142 self.right_extra_button = None
143
144 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
145 self.new_callback = None # called when NEW button pressed, no argument passed in
146 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
147 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
148
149 self.can_return_empty = False
150 self.ignore_OK_button = False # by default do show/use the OK button
151 #------------------------------------------------------------
153 self._LCTRL_items.set_columns(columns = columns)
154 #------------------------------------------------------------
156 self._LCTRL_items.set_column_widths(widths = widths)
157 #------------------------------------------------------------
159 self._LCTRL_items.set_string_items(items = items)
160 self._LCTRL_items.set_column_widths()
161 self._LCTRL_items.Select(0)
162 #------------------------------------------------------------
164 self._LCTRL_items.set_selections(selections = selections)
165 if selections is None:
166 return
167 if len(selections) == 0:
168 return
169 if self.ignore_OK_button:
170 return
171 self._BTN_ok.Enable(True)
172 self._BTN_ok.SetDefault()
173 #------------------------------------------------------------
176 #------------------------------------------------------------
178 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
179 #------------------------------------------------------------
180 # event handlers
181 #------------------------------------------------------------
183 if not self.__ignore_OK_button:
184 self._BTN_ok.SetDefault()
185 self._BTN_ok.Enable(True)
186
187 if self.edit_callback is not None:
188 self._BTN_edit.Enable(True)
189
190 if self.delete_callback is not None:
191 self._BTN_delete.Enable(True)
192 #------------------------------------------------------------
194 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
195 if not self.can_return_empty:
196 self._BTN_cancel.SetDefault()
197 self._BTN_ok.Enable(False)
198 self._BTN_edit.Enable(False)
199 self._BTN_delete.Enable(False)
200 #------------------------------------------------------------
215 #------------------------------------------------------------
232 #------------------------------------------------------------
253 #------------------------------------------------------------
272 #------------------------------------------------------------
291 #------------------------------------------------------------
310 #------------------------------------------------------------
311 # properties
312 #------------------------------------------------------------
326
327 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
328 #------------------------------------------------------------
351
352 left_extra_button = property(lambda x:x, _set_left_extra_button)
353 #------------------------------------------------------------
376
377 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
378 #------------------------------------------------------------
401
402 right_extra_button = property(lambda x:x, _set_right_extra_button)
403 #------------------------------------------------------------
406
408 if callback is not None:
409 if self.refresh_callback is None:
410 raise ValueError('refresh callback must be set before new callback can be set')
411 if not callable(callback):
412 raise ValueError('<new> callback is not a callable: %s' % callback)
413 self.__new_callback = callback
414
415 if callback is None:
416 self._BTN_new.Enable(False)
417 self._BTN_new.Hide()
418 else:
419 self._BTN_new.Enable(True)
420 self._BTN_new.Show()
421
422 new_callback = property(_get_new_callback, _set_new_callback)
423 #------------------------------------------------------------
426
428 if callback is not None:
429 if not callable(callback):
430 raise ValueError('<edit> callback is not a callable: %s' % callback)
431 self.__edit_callback = callback
432
433 if callback is None:
434 self._BTN_edit.Enable(False)
435 self._BTN_edit.Hide()
436 else:
437 self._BTN_edit.Enable(True)
438 self._BTN_edit.Show()
439
440 edit_callback = property(_get_edit_callback, _set_edit_callback)
441 #------------------------------------------------------------
444
446 if callback is not None:
447 if self.refresh_callback is None:
448 raise ValueError('refresh callback must be set before delete callback can be set')
449 if not callable(callback):
450 raise ValueError('<delete> callback is not a callable: %s' % callback)
451 self.__delete_callback = callback
452
453 if callback is None:
454 self._BTN_delete.Enable(False)
455 self._BTN_delete.Hide()
456 else:
457 self._BTN_delete.Enable(True)
458 self._BTN_delete.Show()
459
460 delete_callback = property(_get_delete_callback, _set_delete_callback)
461 #------------------------------------------------------------
464
466 wx.BeginBusyCursor()
467 try:
468 self.refresh_callback(lctrl = self._LCTRL_items)
469 finally:
470 wx.EndBusyCursor()
471 self._LCTRL_items.set_column_widths()
472
474 if callback is not None:
475 if not callable(callback):
476 raise ValueError('<refresh> callback is not a callable: %s' % callback)
477 self.__refresh_callback = callback
478 if callback is not None:
479 wx.CallAfter(self._set_refresh_callback_helper)
480
481 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
482 #------------------------------------------------------------
484 self._LCTRL_items.item_tooltip_callback = callback
485
486 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
487 #def _get_tooltip(self, item): # inside a class
488 #def _get_tooltip(item): # outside a class
489 #------------------------------------------------------------
491 if message is None:
492 self._LBL_message.Hide()
493 return
494 self._LBL_message.SetLabel(message)
495 self._LBL_message.Show()
496
497 message = property(lambda x:x, _set_message)
498 #================================================================
499 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
500
502 """A panel holding a generic multi-column list and action buttions."""
503
505
506 try:
507 msg = kwargs['msg']
508 del kwargs['msg']
509 except KeyError: msg = None
510
511 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
512
513 if msg is None:
514 self._LBL_message.Hide()
515 else:
516 self._LBL_message.SetLabel(msg)
517
518 # new/edit/delete must return True/False to enable refresh
519 self.__new_callback = None # called when NEW button pressed, no argument passed in
520 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
521 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
522 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
523
524 self.__select_callback = None # called when an item is selected, data of topmost selected item passed in
525
526 self.left_extra_button = None
527 self.middle_extra_button = None
528 self.right_extra_button = None
529 #------------------------------------------------------------
530 # external API
531 #------------------------------------------------------------
533 self._LCTRL_items.set_columns(columns = columns)
534 #------------------------------------------------------------
536 self._LCTRL_items.set_string_items(items = items)
537 self._LCTRL_items.set_column_widths()
538
539 if (items is None) or (len(items) == 0):
540 self._BTN_edit.Enable(False)
541 self._BTN_remove.Enable(False)
542 else:
543 self._LCTRL_items.Select(0)
544 #------------------------------------------------------------
547 #------------------------------------------------------------
550 #------------------------------------------------------------
552 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
553 #------------------------------------------------------------
554 # event handlers
555 #------------------------------------------------------------
557 if self.edit_callback is not None:
558 self._BTN_edit.Enable(True)
559 if self.delete_callback is not None:
560 self._BTN_remove.Enable(True)
561 if self.__select_callback is not None:
562 item = self._LCTRL_items.get_selected_item_data(only_one=True)
563 self.__select_callback(item)
564 #------------------------------------------------------------
566 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
567 self._BTN_edit.Enable(False)
568 self._BTN_remove.Enable(False)
569 if self.__select_callback is not None:
570 self.__select_callback(None)
571 #------------------------------------------------------------
582 #------------------------------------------------------------
587 #------------------------------------------------------------
601 #------------------------------------------------------------
615 #------------------------------------------------------------
631 #------------------------------------------------------------
647 #------------------------------------------------------------
663 #------------------------------------------------------------
664 # properties
665 #------------------------------------------------------------
668
670 if callback is not None:
671 if not callable(callback):
672 raise ValueError('<new> callback is not a callable: %s' % callback)
673 self.__new_callback = callback
674 self._BTN_add.Enable(callback is not None)
675
676 new_callback = property(_get_new_callback, _set_new_callback)
677 #------------------------------------------------------------
680
682 if callback is not None:
683 if not callable(callback):
684 raise ValueError('<select> callback is not a callable: %s' % callback)
685 self.__select_callback = callback
686
687 select_callback = property(_get_select_callback, _set_select_callback)
688 #------------------------------------------------------------
691
693 if msg is None:
694 self._LBL_message.Hide()
695 self._LBL_message.SetLabel(u'')
696 else:
697 self._LBL_message.SetLabel(msg)
698 self._LBL_message.Show()
699 self.Layout()
700
701 message = property(_get_message, _set_message)
702 #------------------------------------------------------------
718
719 left_extra_button = property(lambda x:x, _set_left_extra_button)
720 #------------------------------------------------------------
736
737 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
738 #------------------------------------------------------------
754
755 right_extra_button = property(lambda x:x, _set_right_extra_button)
756 #================================================================
757 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
758
760
762
763 try:
764 msg = kwargs['msg']
765 del kwargs['msg']
766 except KeyError:
767 msg = None
768
769 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
770
771 if msg is None:
772 self._LBL_msg.Hide()
773 else:
774 self._LBL_msg.SetLabel(msg)
775
776 self.allow_duplicate_picks = True
777
778 self._LCTRL_left.activate_callback = self.__pick_selected
779 self.__extra_button_callback = None
780
781 self._LCTRL_left.SetFocus()
782 #------------------------------------------------------------
783 # external API
784 #------------------------------------------------------------
786 self._LCTRL_left.set_columns(columns = columns)
787 if columns_right is None:
788 self._LCTRL_right.set_columns(columns = columns)
789 else:
790 if len(columns_right) < len(columns):
791 cols = columns
792 else:
793 cols = columns_right[:len(columns)]
794 self._LCTRL_right.set_columns(columns = cols)
795 #------------------------------------------------------------
797 self._LCTRL_left.set_string_items(items = items)
798 self._LCTRL_left.set_column_widths()
799 self._LCTRL_right.set_string_items()
800
801 self._BTN_left2right.Enable(False)
802 self._BTN_right2left.Enable(False)
803 #------------------------------------------------------------
806 #------------------------------------------------------------
811 #------------------------------------------------------------
813 self._LCTRL_right.set_string_items(picks)
814 self._LCTRL_right.set_column_widths()
815 if data is not None:
816 self._LCTRL_right.set_data(data = data)
817 #------------------------------------------------------------
820 #------------------------------------------------------------
822 return self._LCTRL_right.get_item_data()
823
824 picks = property(get_picks, lambda x:x)
825 #------------------------------------------------------------
841
842 extra_button = property(lambda x:x, _set_extra_button)
843 #------------------------------------------------------------
844 # internal helpers
845 #------------------------------------------------------------
847 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
848 return
849
850 right_items = self._LCTRL_right.get_string_items()
851 right_data = self._LCTRL_right.get_item_data()
852 if right_data is None:
853 right_data = []
854
855 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
856 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
857
858 if self.allow_duplicate_picks:
859 right_items.extend(selected_items)
860 right_data.extend(selected_data)
861 self._LCTRL_right.set_string_items(items = right_items)
862 self._LCTRL_right.set_data(data = right_data)
863 self._LCTRL_right.set_column_widths()
864 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
865 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
866 return
867
868 for sel_item, sel_data in zip(selected_items, selected_data):
869 if sel_item in right_items:
870 continue
871 right_items.append(sel_item)
872 right_data.append(sel_data)
873 self._LCTRL_right.set_string_items(items = right_items)
874 self._LCTRL_right.set_data(data = right_data)
875 self._LCTRL_right.set_column_widths()
876 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
877 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
878 #------------------------------------------------------------
880 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
881 return
882
883 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
884 self._LCTRL_right.remove_item(item_idx)
885
886 if self._LCTRL_right.GetItemCount() == 0:
887 self._BTN_right2left.Enable(False)
888
889 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
890 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
891 #------------------------------------------------------------
892 # event handlers
893 #------------------------------------------------------------
896 #------------------------------------------------------------
898 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
899 self._BTN_left2right.Enable(False)
900 #------------------------------------------------------------
903 #------------------------------------------------------------
905 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
906 self._BTN_right2left.Enable(False)
907 #------------------------------------------------------------
910 #------------------------------------------------------------
913 #------------------------------------------------------------
916 #------------------------------------------------------------
918 self._LCTRL_left.item_tooltip_callback = callback
919
920 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
921 #------------------------------------------------------------
923 self._LCTRL_right.item_tooltip_callback = callback
924
925 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
926
927 #================================================================
928 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin):
929
930 # sorting: at set_string_items() time all items will be
931 # adorned with their initial row number as wxPython data,
932 # this is used later for a) sorting and b) to access
933 # GNUmed data objects associated with rows,
934 # the latter are ordered in initial row number order
935 # at set_data() time
936
937 map_item_idx2data_idx = wx.ListCtrl.GetItemData
938
939 sort_order_tags = {
940 True: u' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
941 False: u' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
942 }
943
945
946 self.debug = None
947
948 try:
949 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
950 except KeyError:
951 kwargs['style'] = wx.LC_REPORT
952
953 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
954
955 wx.ListCtrl.__init__(self, *args, **kwargs)
956 listmixins.ListCtrlAutoWidthMixin.__init__(self)
957
958 # required for column sorting, MUST have this name
959 self._invalidate_sorting_metadata() # must be called after each (external/direct) list item update
960 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?)
961 # for debugging sorting:
962 #self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
963
964 self.__widths = None
965 self.__data = None
966 self.__activate_callback = None
967 self.__rightclick_callback = None
968
969 self.__item_tooltip_callback = None
970 self.__tt_last_item = None
971 self.__tt_static_part = _("""Select the items you want to work on.
972
973 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.""")
974 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
975
976 self.__next_line_to_search = 0
977 self.__search_data = None
978 self.__search_dlg = None
979 self.__searchable_cols = None
980 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
981 self.Bind(wx.EVT_CHAR, self._on_char)
982 self.Bind(wx.EVT_FIND_CLOSE, self._on_search_dlg_closed)
983 self.Bind(wx.EVT_FIND, self._on_search_first_match)
984 self.Bind(wx.EVT_FIND_NEXT, self._on_search_next_match)
985 #------------------------------------------------------------
986 # setters
987 #------------------------------------------------------------
989 """(Re)define the columns.
990
991 Note that this will (have to) delete the items.
992 """
993 self.ClearAll()
994 self.__tt_last_item = None
995 if columns is None:
996 return
997 for idx in range(len(columns)):
998 self.InsertColumn(idx, columns[idx])
999
1000 self._invalidate_sorting_metadata()
1001 #------------------------------------------------------------
1003 """Set the column width policy.
1004
1005 widths = None:
1006 use previous policy if any or default policy
1007 widths != None:
1008 use this policy and remember it for later calls
1009
1010 This means there is no way to *revert* to the default policy :-(
1011 """
1012 # explicit policy ?
1013 if widths is not None:
1014 self.__widths = widths
1015 for idx in range(len(self.__widths)):
1016 self.SetColumnWidth(col = idx, width = self.__widths[idx])
1017 return
1018
1019 # previous policy ?
1020 if self.__widths is not None:
1021 for idx in range(len(self.__widths)):
1022 self.SetColumnWidth(col = idx, width = self.__widths[idx])
1023 return
1024
1025 # default policy !
1026 if self.GetItemCount() == 0:
1027 width_type = wx.LIST_AUTOSIZE_USEHEADER
1028 else:
1029 width_type = wx.LIST_AUTOSIZE
1030 for idx in range(self.GetColumnCount()):
1031 self.SetColumnWidth(col = idx, width = width_type)
1032 #------------------------------------------------------------
1034 """All item members must be unicode()able or None."""
1035
1036 wx.BeginBusyCursor()
1037 self._invalidate_sorting_metadata()
1038
1039 # remove existing items
1040 loop = 0
1041 while True:
1042 if loop > 3:
1043 _log.debug('unable to delete list items after looping 3 times, continuing and hoping for the best')
1044 break
1045 loop += 1
1046 if self.debug is not None:
1047 _log.debug('[round %s] GetItemCount() before DeleteAllItems(): %s (%s, thread [%s])', loop, self.GetItemCount(), self.debug, thread.get_ident())
1048 if not self.DeleteAllItems():
1049 _log.debug('DeleteAllItems() failed (%s)', self.debug)
1050 item_count = self.GetItemCount()
1051 if self.debug is not None:
1052 _log.debug('GetItemCount() after DeleteAllItems(): %s (%s)', item_count, self.debug)
1053 if item_count == 0:
1054 break
1055 wx.SafeYield(None, True)
1056 _log.debug('GetItemCount() not 0 after DeleteAllItems() (%s)', self.debug)
1057 time.sleep(0.3)
1058 wx.SafeYield(None, True)
1059
1060 if items is None:
1061 self.data = None
1062 wx.EndBusyCursor()
1063 return
1064
1065 # insert new items
1066 for item in items:
1067 try:
1068 item[0]
1069 if not isinstance(item, basestring):
1070 is_numerically_iterable = True
1071 # do not iterate over individual chars in a string, however
1072 else:
1073 is_numerically_iterable = False
1074 except TypeError:
1075 is_numerically_iterable = False
1076
1077 if is_numerically_iterable:
1078 # cannot use errors='replace' since then
1079 # None/ints/unicode strings fail to get encoded
1080 col_val = unicode(item[0])
1081 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1082 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1083 col_val = unicode(item[col_num])
1084 self.SetStringItem(index = row_num, col = col_num, label = col_val)
1085 else:
1086 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded
1087 col_val = unicode(item)
1088 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1089
1090 # set data to be a copy of items
1091 self.data = items
1092
1093 wx.EndBusyCursor()
1094 #------------------------------------------------------------
1096 """<data> assumed to be a list corresponding to the item indices"""
1097 if data is not None:
1098 item_count = self.GetItemCount()
1099 if len(data) != item_count:
1100 _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())
1101 for item_idx in range(len(data)):
1102 self.SetItemData(item_idx, item_idx)
1103 self.__data = data
1104 self.__tt_last_item = None
1105 # string data (rows/visible list items) not modified,
1106 # so no need to call _update_sorting_metadata
1107 return
1108
1110 # slower than "return self.__data" but helps with detecting
1111 # problems with len(__data)<>self.GetItemCount()
1112 return self.get_item_data() # returns all data if item_idx is None
1113
1114 data = property(_get_data, set_data)
1115 #------------------------------------------------------------
1117 self.Select(0, on = 0)
1118 if selections is None:
1119 return
1120 for idx in selections:
1121 self.Select(idx = idx, on = 1)
1122
1124 if self.__is_single_selection:
1125 return [self.GetFirstSelected()]
1126 selections = []
1127 idx = self.GetFirstSelected()
1128 while idx != -1:
1129 selections.append(idx)
1130 idx = self.GetNextSelected(idx)
1131 return selections
1132
1133 selections = property(__get_selections, set_selections)
1134 #------------------------------------------------------------
1135 # getters
1136 #------------------------------------------------------------
1138 labels = []
1139 for col_idx in self.GetColumnCount():
1140 col = self.GetColumn(col = col_idx)
1141 labels.append(col.GetText())
1142 return labels
1143 #------------------------------------------------------------
1147 #------------------------------------------------------------
1150 #------------------------------------------------------------
1153 #------------------------------------------------------------
1155
1156 if self.__is_single_selection or only_one:
1157 return self.GetFirstSelected()
1158
1159 items = []
1160 idx = self.GetFirstSelected()
1161 while idx != -1:
1162 items.append(idx)
1163 idx = self.GetNextSelected(idx)
1164
1165 return items
1166 #------------------------------------------------------------
1168
1169 if self.__is_single_selection or only_one:
1170 return self.GetItemText(self.GetFirstSelected())
1171
1172 items = []
1173 idx = self.GetFirstSelected()
1174 while idx != -1:
1175 items.append(self.GetItemText(idx))
1176 idx = self.GetNextSelected(idx)
1177
1178 return items
1179 #------------------------------------------------------------
1181 if self.__data is None: # this isn't entirely clean
1182 return None
1183
1184 if item_idx is not None:
1185 return self.__data[self.map_item_idx2data_idx(item_idx)]
1186
1187 # if <idx> is None return all data up to item_count,
1188 # in case of len(__data) <> self.GetItemCount() this
1189 # gives the chance to figure out what is going on
1190 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1191 #------------------------------------------------------------
1193
1194 if self.__is_single_selection or only_one:
1195 if self.__data is None:
1196 return None
1197 idx = self.GetFirstSelected()
1198 if idx == -1:
1199 return None
1200 return self.__data[self.map_item_idx2data_idx(idx)]
1201
1202 data = []
1203 if self.__data is None:
1204 return data
1205 idx = self.GetFirstSelected()
1206 while idx != -1:
1207 data.append(self.__data[self.map_item_idx2data_idx(idx)])
1208 idx = self.GetNextSelected(idx)
1209
1210 return data
1211 #------------------------------------------------------------
1213 self.Select(idx = self.GetFirstSelected(), on = 0)
1214 #------------------------------------------------------------
1216 # do NOT remove the corresponding data because even if
1217 # the item pointing to this data instance is gone all
1218 # other items will still point to their corresponding
1219 # *initial* row numbers
1220 #if self.__data is not None:
1221 # del self.__data[self.map_item_idx2data_idx(item_idx)]
1222 self.DeleteItem(item_idx)
1223 self.__tt_last_item = None
1224 self._invalidate_sorting_metadata()
1225 #------------------------------------------------------------
1226 # event handlers
1227 #------------------------------------------------------------
1229 event.Skip()
1230 if self.__activate_callback is not None:
1231 self.__activate_callback(event)
1232 #------------------------------------------------------------
1234 event.Skip()
1235 if self.__rightclick_callback is not None:
1236 self.__rightclick_callback(event)
1237 #------------------------------------------------------------
1239
1240 if evt.GetModifiers() != wx.MOD_CMD:
1241 evt.Skip()
1242 return
1243
1244 if unichr(evt.GetRawKeyCode()) != u'f':
1245 evt.Skip()
1246 return
1247
1248 if self.__search_dlg is not None:
1249 self.__search_dlg.Close()
1250 return
1251
1252 if self.__searchable_cols is None:
1253 self.searchable_columns = None
1254
1255 if len(self.__searchable_cols) == 0:
1256 return
1257
1258 if self.__search_data is None:
1259 self.__search_data = wx.FindReplaceData()
1260 self.__search_dlg = wx.FindReplaceDialog (
1261 self,
1262 self.__search_data,
1263 _('Search in list'),
1264 wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD
1265 )
1266 self.__search_dlg.Show(True)
1267 #------------------------------------------------------------
1269 """Update tooltip on mouse motion.
1270
1271 for s in dir(wx):
1272 if s.startswith('LIST_HITTEST'):
1273 print s, getattr(wx, s)
1274
1275 LIST_HITTEST_ABOVE 1
1276 LIST_HITTEST_BELOW 2
1277 LIST_HITTEST_NOWHERE 4
1278 LIST_HITTEST_ONITEM 672
1279 LIST_HITTEST_ONITEMICON 32
1280 LIST_HITTEST_ONITEMLABEL 128
1281 LIST_HITTEST_ONITEMRIGHT 256
1282 LIST_HITTEST_ONITEMSTATEICON 512
1283 LIST_HITTEST_TOLEFT 1024
1284 LIST_HITTEST_TORIGHT 2048
1285 """
1286 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
1287
1288 # pointer on item related area at all ?
1289 if where_flag not in [
1290 wx.LIST_HITTEST_ONITEMLABEL,
1291 wx.LIST_HITTEST_ONITEMICON,
1292 wx.LIST_HITTEST_ONITEMSTATEICON,
1293 wx.LIST_HITTEST_ONITEMRIGHT,
1294 wx.LIST_HITTEST_ONITEM
1295 ]:
1296 self.__tt_last_item = None # not on any item
1297 self.SetToolTipString(self.__tt_static_part)
1298 return
1299
1300 # same item as last time around ?
1301 if self.__tt_last_item == item_idx:
1302 return
1303
1304 # remeber the new item we are on
1305 self.__tt_last_item = item_idx
1306
1307 # HitTest() can return -1 if it so pleases, meaning that no item
1308 # was hit or else that maybe there aren't any items (empty list)
1309 if item_idx == wx.NOT_FOUND:
1310 self.SetToolTipString(self.__tt_static_part)
1311 return
1312
1313 # do we *have* item data ?
1314 if self.__data is None:
1315 self.SetToolTipString(self.__tt_static_part)
1316 return
1317
1318 # under some circumstances the item_idx returned
1319 # by HitTest() may be out of bounds with respect to
1320 # self.__data, this hints at a sync problem between
1321 # setting display items and associated data
1322 if (
1323 (item_idx > (len(self.__data) - 1))
1324 or
1325 (item_idx < -1)
1326 ):
1327 self.SetToolTipString(self.__tt_static_part)
1328 print "*************************************************************"
1329 print "GNUmed has detected an inconsistency with list item tooltips."
1330 print ""
1331 print "This is not a big problem and you can keep working."
1332 print ""
1333 print "However, please send us the following so we can fix GNUmed:"
1334 print ""
1335 print "item idx: %s" % item_idx
1336 print 'where flag: %s' % where_flag
1337 print 'data list length: %s' % len(self.__data)
1338 print "*************************************************************"
1339 return
1340
1341 dyna_tt = None
1342 if self.__item_tooltip_callback is not None:
1343 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
1344
1345 if dyna_tt is None:
1346 self.SetToolTipString(self.__tt_static_part)
1347 return
1348
1349 self.SetToolTipString(dyna_tt)
1350 #------------------------------------------------------------
1351 # search related methods
1352 #------------------------------------------------------------
1356 #------------------------------------------------------------
1357 # def _on_lost_focus(self, evt):
1358 # evt.Skip()
1359 # if self.__search_dlg is None:
1360 # return
1361 ## print self.FindFocus()
1362 ## print self.__search_dlg
1363 # #self.__search_dlg.Close()
1364 #------------------------------------------------------------
1366 for row_idx in range(self.__next_line_to_search, self.ItemCount):
1367 for col_idx in range(self.ColumnCount):
1368 if col_idx not in self.__searchable_cols:
1369 continue
1370 col_val = self.GetItem(row_idx, col_idx).GetText()
1371 if regex.search(search_term, col_val, regex.U | regex.I) is not None:
1372 self.Select(row_idx)
1373 self.EnsureVisible(row_idx)
1374 if row_idx == self.ItemCount - 1:
1375 # wrap around
1376 self.__next_line_to_search = 0
1377 else:
1378 self.__next_line_to_search = row_idx + 1
1379 return True
1380 # wrap around
1381 self.__next_line_to_search = 0
1382 return False
1383 #------------------------------------------------------------
1386 #------------------------------------------------------------
1389 #------------------------------------------------------------
1390 # properties
1391 #------------------------------------------------------------
1394
1396 if callback is None:
1397 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1398 else:
1399 if not callable(callback):
1400 raise ValueError('<activate> callback is not a callable: %s' % callback)
1401 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1402 self.__activate_callback = callback
1403
1404 activate_callback = property(_get_activate_callback, _set_activate_callback)
1405 #------------------------------------------------------------
1408
1410 if callback is None:
1411 self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK)
1412 else:
1413 if not callable(callback):
1414 raise ValueError('<rightclick> callback is not a callable: %s' % callback)
1415 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
1416 self.__rightclick_callback = callback
1417
1418 rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback)
1419 #------------------------------------------------------------
1421 if callback is not None:
1422 if not callable(callback):
1423 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
1424 self.__item_tooltip_callback = callback
1425
1426 # the callback must be a function which takes a single argument
1427 # the argument is the data for the item the tooltip is on
1428 # the callback must return None if no item tooltip is to be shown
1429 # otherwise it must return a string (possibly with \n)
1430 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1431 #------------------------------------------------------------
1433 # zero-based list of which columns to search
1434 if cols is None:
1435 self.__searchable_cols = range(self.ColumnCount)
1436 return
1437 # weed out columns to be searched which
1438 # don't exist and uniquify them
1439 new_cols = {}
1440 for col in cols:
1441 if col < self.ColumnCount:
1442 new_cols[col] = True
1443 self.__searchable_cols = new_cols.keys()
1444
1445 searchable_columns = property(lambda x:x, _set_searchable_cols)
1446 #------------------------------------------------------------
1447 # ColumnSorterMixin API
1448 #------------------------------------------------------------
1453 #------------------------------------------------------------
1455 self._cleanup_column_headers()
1456 # annotate sort column
1457 col_idx, is_ascending = self.GetSortState()
1458 col_state = self.GetColumn(col_idx)
1459 col_state.m_text += self.sort_order_tags[is_ascending]
1460 self.SetColumn(col_idx, col_state)
1461 #------------------------------------------------------------
1463 dict2sort = {}
1464 item_count = self.GetItemCount()
1465 if item_count == 0:
1466 return dict2sort
1467 col_count = self.GetColumnCount()
1468 for item_idx in range(item_count):
1469 dict2sort[item_idx] = ()
1470 if col_count == 0:
1471 continue
1472 for col_idx in range(col_count):
1473 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
1474
1475 return dict2sort
1476 #------------------------------------------------------------
1478 for col_idx in range(self.ColumnCount):
1479 col_state = self.GetColumn(col_idx)
1480 if col_state.m_text.endswith(self.sort_order_tags[True]):
1481 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[True])]
1482 if col_state.m_text.endswith(self.sort_order_tags[False]):
1483 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[False])]
1484 self.SetColumn(col_idx, col_state)
1485 #------------------------------------------------------------
1487 self.itemDataMap = None
1488 self.SetColumnCount(self.GetColumnCount())
1489 self._cleanup_column_headers()
1490 #------------------------------------------------------------
1493 #------------------------------------------------------------
1495 # for debugging:
1496 # print "column clicked : %s" % (event.GetColumn())
1497 # column, order = self.GetSortState()
1498 # print "column %s sort %s" % (column, order)
1499 # print self._colSortFlag
1500 # print self.itemDataMap
1501 event.Skip()
1502
1503 #================================================================
1504 # main
1505 #----------------------------------------------------------------
1506 if __name__ == '__main__':
1507
1508 if len(sys.argv) < 2:
1509 sys.exit()
1510
1511 if sys.argv[1] != 'test':
1512 sys.exit()
1513
1514 sys.path.insert(0, '../../')
1515
1516 from Gnumed.pycommon import gmI18N
1517 gmI18N.activate_locale()
1518 gmI18N.install_domain()
1519
1520 #------------------------------------------------------------
1522 app = wx.PyWidgetTester(size = (400, 500))
1523 dlg = wx.MultiChoiceDialog (
1524 parent = None,
1525 message = 'test message',
1526 caption = 'test caption',
1527 choices = ['a', 'b', 'c', 'd', 'e']
1528 )
1529 dlg.ShowModal()
1530 sels = dlg.GetSelections()
1531 print "selected:"
1532 for sel in sels:
1533 print sel
1534 #------------------------------------------------------------
1540
1541 def refresh(lctrl):
1542 choices = ['a', 'b', 'c']
1543 lctrl.set_string_items(choices)
1544
1545 app = wx.PyWidgetTester(size = (200, 50))
1546 chosen = get_choices_from_list (
1547 # msg = 'select a health issue\nfrom the list below\n',
1548 caption = 'select health issues',
1549 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
1550 #columns = ['issue', 'no of episodes']
1551 columns = ['issue'],
1552 refresh_callback = refresh
1553 #, edit_callback = edit
1554 )
1555 print "chosen:"
1556 print chosen
1557 #------------------------------------------------------------
1559 app = wx.PyWidgetTester(size = (200, 50))
1560 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1561 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1562 #dlg.set_columns(['Plugins'], [])
1563 dlg.set_string_items(['patient', 'emr', 'docs'])
1564 result = dlg.ShowModal()
1565 print result
1566 print dlg.get_picks()
1567 #------------------------------------------------------------
1568 #test_get_choices_from_list()
1569 #test_wxMultiChoiceDialog()
1570 test_item_picker_dlg()
1571
1572 #================================================================
1573 #
1574
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Aug 3 03:56:25 2013 | http://epydoc.sourceforge.net |