| Home | Trees | Indices | Help |
|
|---|
|
|
1 __doc__ = """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 threading
25 import time
26 import locale
27 import os
28 import io
29 import csv
30 import re as regex
31 import datetime as pydt
32
33
34 import wx
35 import wx.lib.mixins.listctrl as listmixins
36
37
38 if __name__ == '__main__':
39 sys.path.insert(0, '../../')
40 from Gnumed.pycommon import gmTools
41 from Gnumed.pycommon import gmDispatcher
42 from Gnumed.wxpython.gmGuiHelpers import decorate_window_title, undecorate_window_title
43
44
45 _log = logging.getLogger('gm.list_ui')
46 #================================================================
47 # FIXME: configurable callback on double-click action
48
49 -def get_choices_from_list (
50 parent=None,
51 msg=None,
52 caption=None,
53 columns=None,
54 choices=None,
55 data=None,
56 selections=None,
57 edit_callback=None,
58 new_callback=None,
59 delete_callback=None,
60 refresh_callback=None,
61 single_selection=False,
62 can_return_empty=False,
63 ignore_OK_button=False,
64 left_extra_button=None,
65 middle_extra_button=None,
66 right_extra_button=None,
67 list_tooltip_callback=None):
68 """Let user select item(s) from a list.
69
70 - new_callback: ()
71 - edit_callback: (item data)
72 - delete_callback: (item data)
73 - refresh_callback: (listctrl)
74 - list_tooltip_callback: (item data)
75
76 - left/middle/right_extra_button: (label, tooltip, <callback> [, wants_list_ctrl])
77 <wants_list_ctrl> is optional
78 <callback> is called with item_data (or listctrl) as the only argument
79 if <callback> returns TRUE, the listctrl will be refreshed, if a refresh_callback is available
80
81 returns:
82 on [CANCEL]: None
83 on [OK]:
84 if any items selected:
85 if single_selection:
86 the data of the selected item
87 else:
88 list of data of selected items
89 else:
90 if can_return_empty is True AND [OK] button was pressed:
91 empty list
92 else:
93 None
94 """
95 caption = decorate_window_title(gmTools.coalesce(caption, _('generic multi choice dialog')))
96
97 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, single_selection = single_selection)
98 dlg.refresh_callback = refresh_callback
99 dlg.edit_callback = edit_callback
100 dlg.new_callback = new_callback
101 dlg.delete_callback = delete_callback
102 dlg.list_tooltip_callback = list_tooltip_callback
103
104 dlg.can_return_empty = can_return_empty
105 dlg.ignore_OK_button = ignore_OK_button
106 dlg.left_extra_button = left_extra_button
107 dlg.middle_extra_button = middle_extra_button
108 dlg.right_extra_button = right_extra_button
109
110 dlg.set_columns(columns = columns)
111
112 if refresh_callback is None:
113 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible
114 dlg.set_column_widths()
115
116 if data is not None:
117 dlg.set_data(data = data) # can override data set if refresh_callback is not None
118
119 if selections is not None:
120 if single_selection:
121 dlg.set_selections(selections = selections[:1])
122 else:
123 dlg.set_selections(selections = selections)
124
125 btn_pressed = dlg.ShowModal()
126 sels = dlg.get_selected_item_data(only_one = single_selection)
127 dlg.DestroyLater()
128
129 if btn_pressed == wx.ID_OK:
130 if can_return_empty and (sels is None):
131 return []
132 return sels
133
134 return None
135
136 #----------------------------------------------------------------
137 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
138
140 """A dialog holding a list and a few buttons to act on the items."""
141
143
144 try:
145 msg = kwargs['msg']
146 del kwargs['msg']
147 except KeyError:
148 msg = None
149
150 try:
151 title = kwargs['title']
152 except KeyError:
153 title = self.__class__.__name__
154 kwargs['title'] = decorate_window_title(title)
155
156 try:
157 single_selection = kwargs['single_selection']
158 del kwargs['single_selection']
159 except KeyError:
160 single_selection = False
161
162 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
163
164 self.message = msg
165
166 self.left_extra_button = None
167 self.middle_extra_button = None
168 self.right_extra_button = None
169
170 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
171 self.new_callback = None # called when NEW button pressed, no argument passed in
172 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
173 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
174
175 self.can_return_empty = False
176 self.ignore_OK_button = False # by default do show/use the OK button
177
178 self.select_callback = None # called when an item is selected, data of topmost selected item passed in
179 self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
180 if single_selection:
181 self._LCTRL_items.SetSingleStyle(wx.LC_SINGLE_SEL, add = True)
182
183 #------------------------------------------------------------
185 self._LCTRL_items.set_columns(columns = columns)
186
187 #------------------------------------------------------------
189 self._LCTRL_items.set_column_widths(widths = widths)
190
191 #------------------------------------------------------------
193 self._LCTRL_items.set_string_items(items = items, reshow = reshow)
194 self._LCTRL_items.set_column_widths()
195 #self._LCTRL_items.Select(0)
196
197 #------------------------------------------------------------
199 self._LCTRL_items.set_selections(selections = selections)
200 if selections is None:
201 return
202 if len(selections) == 0:
203 return
204 if self.ignore_OK_button:
205 return
206 self._BTN_ok.Enable(True)
207 self._BTN_ok.SetDefault()
208
209 #------------------------------------------------------------
212
213 #------------------------------------------------------------
215 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
216
217 #------------------------------------------------------------
218 # event handlers
219 #------------------------------------------------------------
221 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
222 if not self.can_return_empty:
223 self._BTN_cancel.SetDefault()
224 self._BTN_ok.Enable(False)
225 self._BTN_edit.Enable(False)
226 self._BTN_delete.Enable(False)
227
228 event.Skip()
229
230 #------------------------------------------------------------
234
235 #------------------------------------------------------------
241
242 #------------------------------------------------------------
271
272 #------------------------------------------------------------
291
292 #------------------------------------------------------------
311
312 #------------------------------------------------------------
331
332 #------------------------------------------------------------
333 # internal helpers
334 #------------------------------------------------------------
336 event.Skip()
337 if not self.__ignore_OK_button:
338 self._BTN_ok.SetDefault()
339 self._BTN_ok.Enable(True)
340 if self.edit_callback is not None:
341 self._BTN_edit.Enable(True)
342 if self.delete_callback is not None:
343 self._BTN_delete.Enable(True)
344 if self.__select_callback is not None:
345 item = self._LCTRL_items.get_selected_item_data(only_one = True)
346 self.__select_callback(item)
347
348 #------------------------------------------------------------
351
352 #------------------------------------------------------------
354 any_deleted = False
355 for item_data in self._LCTRL_items.get_selected_item_data(only_one = False):
356 if item_data is None:
357 continue
358 if self.__delete_callback(item_data):
359 any_deleted = True
360
361 self._LCTRL_items.SetFocus()
362
363 if any_deleted is False:
364 return
365
366 if self.__refresh_callback is None:
367 return
368
369 wx.BeginBusyCursor()
370 try:
371 self.__refresh_callback(lctrl = self._LCTRL_items)
372 self._LCTRL_items.set_column_widths()
373 finally:
374 wx.EndBusyCursor()
375
376 #------------------------------------------------------------
379
380 #------------------------------------------------------------
382 if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
383 self._LCTRL_items.SetFocus()
384 return
385 if self.__refresh_callback is None:
386 self._LCTRL_items.SetFocus()
387 return
388 wx.BeginBusyCursor()
389 try:
390 self.__refresh_callback(lctrl = self._LCTRL_items)
391 self._LCTRL_items.set_column_widths()
392 self._LCTRL_items.SetFocus()
393 finally:
394 wx.EndBusyCursor()
395
396 #------------------------------------------------------------
399
400 #------------------------------------------------------------
402 if not self.__new_callback():
403 self._LCTRL_items.SetFocus()
404 return
405 if self.__refresh_callback is None:
406 self._LCTRL_items.SetFocus()
407 return
408 wx.BeginBusyCursor()
409 try:
410 self.__refresh_callback(lctrl = self._LCTRL_items)
411 self._LCTRL_items.set_column_widths()
412 self._LCTRL_items.SetFocus()
413 finally:
414 wx.EndBusyCursor()
415
416 #------------------------------------------------------------
417 # properties
418 #------------------------------------------------------------
432
433 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
434
435 #------------------------------------------------------------
458
459 left_extra_button = property(lambda x:x, _set_left_extra_button)
460
461 #------------------------------------------------------------
484
485 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
486
487 #------------------------------------------------------------
510
511 right_extra_button = property(lambda x:x, _set_right_extra_button)
512
513 #------------------------------------------------------------
516
518 if callback is not None:
519 if self.__refresh_callback is None:
520 raise ValueError('refresh callback must be set before new callback can be set')
521 if not callable(callback):
522 raise ValueError('<new> callback is not a callable: %s' % callback)
523 self.__new_callback = callback
524
525 if callback is None:
526 self._BTN_new.Enable(False)
527 self._BTN_new.Hide()
528 self._LCTRL_items.new_callback = None
529 else:
530 self._BTN_new.Enable(True)
531 self._BTN_new.Show()
532 self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
533
534 new_callback = property(_get_new_callback, _set_new_callback)
535
536 #------------------------------------------------------------
539
541 if callback is not None:
542 if not callable(callback):
543 raise ValueError('<edit> callback is not a callable: %s' % callback)
544 self.__edit_callback = callback
545
546 if callback is None:
547 self._BTN_edit.Enable(False)
548 self._BTN_edit.Hide()
549 self._LCTRL_items.edit_callback = None
550 else:
551 self._BTN_edit.Enable(True)
552 self._BTN_edit.Show()
553 self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
554
555 edit_callback = property(_get_edit_callback, _set_edit_callback)
556
557 #------------------------------------------------------------
560
562 if callback is not None:
563 if self.__refresh_callback is None:
564 raise ValueError('refresh callback must be set before delete callback can be set')
565 if not callable(callback):
566 raise ValueError('<delete> callback is not a callable: %s' % callback)
567 self.__delete_callback = callback
568 if callback is None:
569 self._BTN_delete.Enable(False)
570 self._BTN_delete.Hide()
571 self._LCTRL_items.delete_callback = None
572 else:
573 self._BTN_delete.Enable(True)
574 self._BTN_delete.Show()
575 self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
576
577 delete_callback = property(_get_delete_callback, _set_delete_callback)
578
579 #------------------------------------------------------------
582
584 wx.BeginBusyCursor()
585 try:
586 self.__refresh_callback(lctrl = self._LCTRL_items)
587 finally:
588 wx.EndBusyCursor()
589 self._LCTRL_items.set_column_widths()
590
592 if callback is not None:
593 if not callable(callback):
594 raise ValueError('<refresh> callback is not a callable: %s' % callback)
595 self.__refresh_callback = callback
596 if callback is not None:
597 wx.CallAfter(self._set_refresh_callback_helper)
598
599 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
600
601 #------------------------------------------------------------
604
606 if callback is not None:
607 if not callable(callback):
608 raise ValueError('<select> callback is not a callable: %s' % callback)
609 self.__select_callback = callback
610
611 select_callback = property(_get_select_callback, _set_select_callback)
612
613 #------------------------------------------------------------
615 self._LCTRL_items.item_tooltip_callback = callback
616
617 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
618 #def _get_tooltip(self, item): # inside a class
619 #def _get_tooltip(item): # outside a class
620 #------------------------------------------------------------
622 if message is None:
623 self._LBL_message.Hide()
624 return
625 self._LBL_message.SetLabel(message)
626 self._LBL_message.Show()
627
628 message = property(lambda x:x, _set_message)
629
630 #================================================================
631 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
632
634 """A panel holding a generic multi-column list and action buttions."""
635
637
638 try:
639 msg = kwargs['msg']
640 del kwargs['msg']
641 except KeyError: msg = None
642
643 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
644
645 if msg is None:
646 self._LBL_message.Hide()
647 else:
648 self._LBL_message.SetLabel(msg)
649
650 self.left_extra_button = None
651 self.middle_extra_button = None
652 self.right_extra_button = None
653
654 # new/edit/delete must return True/False to enable refresh
655 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
656 self.new_callback = None # called when NEW button pressed, no argument passed in
657 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
658 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
659
660 self.select_callback = None # called when an item is selected, data of topmost selected item passed in
661 self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
662
663 #------------------------------------------------------------
664 # external API
665 #------------------------------------------------------------
667 self._LCTRL_items.set_columns(columns = columns)
668
669 #------------------------------------------------------------
671 self._LCTRL_items.set_string_items(items = items, reshow = reshow)
672 self._LCTRL_items.set_column_widths()
673
674 if (items is None) or (len(items) == 0):
675 self._BTN_edit.Enable(False)
676 self._BTN_remove.Enable(False)
677 #else:
678 # self._LCTRL_items.Select(0)
679
680 #------------------------------------------------------------
683
684 #------------------------------------------------------------
687
688 #------------------------------------------------------------
690 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
691
692 #------------------------------------------------------------
693 # internal helpers
694 #------------------------------------------------------------
696 event.Skip()
697 if self.__edit_callback is not None:
698 self._BTN_edit.Enable(True)
699 if self.__delete_callback is not None:
700 self._BTN_remove.Enable(True)
701 if self.__select_callback is not None:
702 item = self._LCTRL_items.get_selected_item_data(only_one = True)
703 self.__select_callback(item)
704
705 #------------------------------------------------------------
708
709 #------------------------------------------------------------
711 if not self.__delete_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
712 return
713 if self.__refresh_callback is None:
714 self._LCTRL_items.SetFocus()
715 return
716 wx.BeginBusyCursor()
717 try:
718 self.__refresh_callback(lctrl = self._LCTRL_items)
719 self._LCTRL_items.set_column_widths()
720 self._LCTRL_items.SetFocus()
721 finally:
722 wx.EndBusyCursor()
723
724 #------------------------------------------------------------
727
728 #------------------------------------------------------------
730 if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
731 self._LCTRL_items.SetFocus()
732 return
733 if self.__refresh_callback is None:
734 self._LCTRL_items.SetFocus()
735 return
736 wx.BeginBusyCursor()
737 try:
738 self.__refresh_callback(lctrl = self._LCTRL_items)
739 self._LCTRL_items.set_column_widths()
740 self._LCTRL_items.SetFocus()
741 finally:
742 wx.EndBusyCursor()
743
744 #------------------------------------------------------------
747
748 #------------------------------------------------------------
750 if not self.__new_callback():
751 self._LCTRL_items.SetFocus()
752 return
753 if self.__refresh_callback is None:
754 self._LCTRL_items.SetFocus()
755 return
756 wx.BeginBusyCursor()
757 try:
758 self.__refresh_callback(lctrl = self._LCTRL_items)
759 self._LCTRL_items.set_column_widths()
760 self._LCTRL_items.SetFocus()
761 finally:
762 wx.EndBusyCursor()
763
764 #------------------------------------------------------------
765 # event handlers
766 #------------------------------------------------------------
768 event.Skip()
769 if self._LCTRL_items.get_selected_items(only_one = True) == -1:
770 self._BTN_edit.Enable(False)
771 self._BTN_remove.Enable(False)
772 if self.__select_callback is not None:
773 self.__select_callback(None)
774
775 #------------------------------------------------------------
777 event.Skip()
778 if self.__edit_callback is None:
779 return
780 self._on_edit_button_pressed(event)
781
782 #------------------------------------------------------------
793
794 #------------------------------------------------------------
808
809 #------------------------------------------------------------
814
815 #------------------------------------------------------------
831
832 #------------------------------------------------------------
848
849 #------------------------------------------------------------
865
866 #------------------------------------------------------------
867 # properties
868 #------------------------------------------------------------
871
873 if callback is not None:
874 if self.__refresh_callback is None:
875 raise ValueError('refresh callback must be set before new callback can be set')
876 if not callable(callback):
877 raise ValueError('<new> callback is not a callable: %s' % callback)
878 self.__new_callback = callback
879
880 if callback is None:
881 self._BTN_add.Enable(False)
882 self._BTN_add.Hide()
883 self._LCTRL_items.new_callback = None
884 else:
885 self._BTN_add.Enable(True)
886 self._BTN_add.Show()
887 self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
888
889 new_callback = property(_get_new_callback, _set_new_callback)
890
891 #------------------------------------------------------------
894
896 if callback is not None:
897 if not callable(callback):
898 raise ValueError('<edit> callback is not a callable: %s' % callback)
899 self.__edit_callback = callback
900
901 if callback is None:
902 self._BTN_edit.Enable(False)
903 self._BTN_edit.Hide()
904 self._LCTRL_items.edit_callback = None
905 else:
906 self._BTN_edit.Enable(True)
907 self._BTN_edit.Show()
908 self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
909
910 edit_callback = property(_get_edit_callback, _set_edit_callback)
911
912 #------------------------------------------------------------
915
917 if callback is not None:
918 if self.__refresh_callback is None:
919 raise ValueError('refresh callback must be set before delete callback can be set')
920 if not callable(callback):
921 raise ValueError('<delete> callback is not a callable: %s' % callback)
922 self.__delete_callback = callback
923 if callback is None:
924 self._BTN_remove.Enable(False)
925 self._BTN_remove.Hide()
926 self._LCTRL_items.delete_callback = None
927 else:
928 self._BTN_remove.Enable(True)
929 self._BTN_remove.Show()
930 self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
931
932 delete_callback = property(_get_delete_callback, _set_delete_callback)
933
934 #------------------------------------------------------------
937
939 wx.BeginBusyCursor()
940 try:
941 self.__refresh_callback(lctrl = self._LCTRL_items)
942 finally:
943 wx.EndBusyCursor()
944 self._LCTRL_items.set_column_widths()
945
947 if callback is not None:
948 if not callable(callback):
949 raise ValueError('<refresh> callback is not a callable: %s' % callback)
950 self.__refresh_callback = callback
951 if callback is not None:
952 wx.CallAfter(self._set_refresh_callback_helper)
953
954 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
955
956 #------------------------------------------------------------
959
961 if callback is not None:
962 if not callable(callback):
963 raise ValueError('<select> callback is not a callable: %s' % callback)
964 self.__select_callback = callback
965
966 select_callback = property(_get_select_callback, _set_select_callback)
967
968 #------------------------------------------------------------
970 return self._LBL_message.GetLabel()
971
973 if msg is None:
974 self._LBL_message.Hide()
975 self._LBL_message.SetLabel('')
976 else:
977 self._LBL_message.SetLabel(msg)
978 self._LBL_message.Show()
979 self.Layout()
980
981 message = property(_get_message, _set_message)
982
983 #------------------------------------------------------------
999
1000 left_extra_button = property(lambda x:x, _set_left_extra_button)
1001
1002 #------------------------------------------------------------
1018
1019 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
1020
1021 #------------------------------------------------------------
1037
1038 right_extra_button = property(lambda x:x, _set_right_extra_button)
1039
1040 #================================================================
1041 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
1042
1044
1046
1047 try:
1048 msg = kwargs['msg']
1049 del kwargs['msg']
1050 except KeyError:
1051 msg = None
1052
1053 try:
1054 title = kwargs['title']
1055 except KeyError:
1056 title = self.__class__.__name__
1057 kwargs['title'] = decorate_window_title(title)
1058
1059 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
1060
1061 if msg is None:
1062 self._LBL_msg.Hide()
1063 else:
1064 self._LBL_msg.SetLabel(msg)
1065
1066 self.ignore_dupes_on_picking = True
1067
1068 self._LCTRL_left.activate_callback = self.__pick_selected
1069 self.__extra_button_callback = None
1070
1071 self._LCTRL_left.SetFocus()
1072
1073 #------------------------------------------------------------
1074 # external API
1075 #------------------------------------------------------------
1077 self._LCTRL_left.set_columns(columns = columns)
1078 if columns_right is None:
1079 self._LCTRL_right.set_columns(columns = columns)
1080 return
1081
1082 if len(columns_right) < len(columns):
1083 cols = columns
1084 else:
1085 cols = columns_right[:len(columns)]
1086 self._LCTRL_right.set_columns(columns = cols)
1087
1088 #------------------------------------------------------------
1090 self._LCTRL_left.set_string_items(items = items, reshow = reshow)
1091 self._LCTRL_left.set_column_widths()
1092 self._LCTRL_right.set_string_items(reshow = False)
1093
1094 self._BTN_left2right.Enable(False)
1095 self._BTN_right2left.Enable(False)
1096
1097 #------------------------------------------------------------
1100
1101 #------------------------------------------------------------
1103 self.set_string_items(items = choices, reshow = reshow)
1104 if data is not None:
1105 self.set_data(data = data)
1106
1107 #------------------------------------------------------------
1109 self._LCTRL_right.set_string_items(picks, reshow = reshow)
1110 self._LCTRL_right.set_column_widths()
1111 if data is not None:
1112 self._LCTRL_right.set_data(data = data)
1113
1114 #------------------------------------------------------------
1117
1118 #------------------------------------------------------------
1120 return self._LCTRL_right.get_item_data()
1121
1122 picks = property(get_picks, lambda x:x)
1123
1124 #------------------------------------------------------------
1140
1141 extra_button = property(lambda x:x, _set_extra_button)
1142
1143 #------------------------------------------------------------
1144 # internal helpers
1145 #------------------------------------------------------------
1147 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
1148 return
1149
1150 right_items = self._LCTRL_right.get_string_items()
1151 right_data = self._LCTRL_right.get_item_data()
1152 if right_data is None:
1153 right_data = []
1154
1155 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
1156 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
1157
1158 if self.ignore_dupes_on_picking is False:
1159 right_items.extend(selected_items)
1160 right_data.extend(selected_data)
1161 self._LCTRL_right.set_string_items(items = right_items, reshow = True)
1162 self._LCTRL_right.set_data(data = right_data)
1163 self._LCTRL_right.set_column_widths()
1164 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
1165 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
1166 return
1167
1168 for sel_item, sel_data in zip(selected_items, selected_data):
1169 if sel_item in right_items:
1170 continue
1171 right_items.append(sel_item)
1172 right_data.append(sel_data)
1173 self._LCTRL_right.set_string_items(items = right_items, reshow = True)
1174 self._LCTRL_right.set_data(data = right_data)
1175 self._LCTRL_right.set_column_widths()
1176 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
1177 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
1178
1179 #------------------------------------------------------------
1181 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
1182 return
1183
1184 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
1185 self._LCTRL_right.remove_item(item_idx)
1186
1187 if self._LCTRL_right.GetItemCount() == 0:
1188 self._BTN_right2left.Enable(False)
1189
1190 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
1191 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
1192
1193 #------------------------------------------------------------
1194 # event handlers
1195 #------------------------------------------------------------
1197 self._BTN_left2right.Enable(True)
1198 #------------------------------------------------------------
1200 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
1201 self._BTN_left2right.Enable(False)
1202 #------------------------------------------------------------
1204 self._BTN_right2left.Enable(True)
1205 #------------------------------------------------------------
1207 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
1208 self._BTN_right2left.Enable(False)
1209 #------------------------------------------------------------
1212 #------------------------------------------------------------
1215 #------------------------------------------------------------
1218 #------------------------------------------------------------
1220 self._LCTRL_left.item_tooltip_callback = callback
1221
1222 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
1223 #------------------------------------------------------------
1225 self._LCTRL_right.item_tooltip_callback = callback
1226
1227 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
1228
1229 #================================================================
1230 -class cReportListCtrl(listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin, wx.ListCtrl):
1231
1232 # sorting: at set_string_items() time all items will be
1233 # adorned with their initial row number as wxPython data,
1234 # this is used later for a) sorting and b) to access
1235 # GNUmed data objects associated with rows,
1236 # the latter are ordered in initial row number order
1237 # at set_data() time
1238
1239 sort_order_tags = {
1240 True: ' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
1241 False: ' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
1242 }
1243
1245
1246 self.debug = None
1247 self.map_item_idx2data_idx = self.GetItemData
1248
1249 try:
1250 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
1251 except KeyError:
1252 kwargs['style'] = wx.LC_REPORT
1253
1254 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
1255
1256 wx.ListCtrl.__init__(self, *args, **kwargs)
1257 listmixins.ListCtrlAutoWidthMixin.__init__(self)
1258
1259 # required for column sorting
1260 self._invalidate_sorting_metadata() # must be called after each (external/direct) list item update
1261 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?)
1262 self.__secondary_sort_col = None
1263 # apparently, this MUST be bound under wxp3 - but why ??
1264 self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
1265
1266 # cols/rows
1267 self.__widths = None
1268 self.__data = None
1269
1270 # event callbacks
1271 self.__select_callback = None
1272 self.__deselect_callback = None
1273 self.__activate_callback = None
1274 self.__new_callback = None
1275 self.__edit_callback = None
1276 self.__delete_callback = None
1277
1278 # context menu
1279 self.__extend_popup_menu_callback = None
1280 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked) # (also handled by MENU key on EVT_LIST_KEY_DOWN)
1281
1282 # row tooltips
1283 self.__item_tooltip_callback = None
1284 self.__tt_last_item = None
1285 # self.__tt_static_part_base = _(
1286 # u'Select the items you want to work on.\n'
1287 # u'\n'
1288 # u'A discontinuous selection may depend on your holding '
1289 # u'down a platform-dependent modifier key (<CTRL>, <ALT>, '
1290 # u'etc) or key combination (eg. <CTRL-SHIFT> or <CTRL-ALT>) '
1291 # u'while clicking.'
1292 # )
1293 self.__tt_static_part_base = ''
1294 self.__tt_static_part = self.__tt_static_part_base
1295 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
1296
1297 # search related:
1298 self.__search_term = None
1299 self.__next_line_to_search = 0
1300 self.__searchable_cols = None
1301
1302 # general event handling
1303 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
1304 self.Bind(wx.EVT_CHAR, self._on_char) # CTRL-F / CTRL-N (LIST_KEY_DOWN does not support modifiers)
1305 self.Bind(wx.EVT_LIST_KEY_DOWN, self._on_list_key_down) # context menu key -> context menu / DEL / INS
1306
1307 #------------------------------------------------------------
1308 # debug sizing
1309 #------------------------------------------------------------
1311 if self.debug is None:
1312 return False
1313 if not self.debug.endswith('_sizing'):
1314 return False
1315 _log.debug('[%s.%s]: *args = (%s), **kwargs = (%s)', self.debug, caller_name, str(args), str(kwargs))
1316 return True
1317
1318 #------------------------------------------------------------
1320 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1321 return super(cReportListCtrl, self).CacheBestSize(*args, **kwargs)
1322
1323 #------------------------------------------------------------
1325 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1326 return super(cReportListCtrl, self).Fit(*args, **kwargs)
1327
1328 #------------------------------------------------------------
1330 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1331 return super(cReportListCtrl, self).FitInside(*args, **kwargs)
1332
1333 #------------------------------------------------------------
1335 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1336 return super(cReportListCtrl, self).InvalidateBestSize(*args, **kwargs)
1337
1338 #------------------------------------------------------------
1340 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1341 return super(cReportListCtrl, self).SetBestFittingSize(*args, **kwargs)
1342
1343 #------------------------------------------------------------
1345 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1346 return super(cReportListCtrl, self).SetInitialSize(*args, **kwargs)
1347
1348 #------------------------------------------------------------
1350 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1351 return super(cReportListCtrl, self).SetClientSize(*args, **kwargs)
1352
1353 #------------------------------------------------------------
1355 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1356 return super(cReportListCtrl, self).SetClientSizeWH(*args, **kwargs)
1357
1358 #------------------------------------------------------------
1360 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1361 return super(cReportListCtrl, self).SetMaxClientSize(*args, **kwargs)
1362
1363 #------------------------------------------------------------
1365 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1366 return super(cReportListCtrl, self).SetMaxSize(*args, **kwargs)
1367
1368 #------------------------------------------------------------
1370 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1371 return super(cReportListCtrl, self).SetMinClientSize(*args, **kwargs)
1372
1373 #------------------------------------------------------------
1375 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1376 return super(cReportListCtrl, self).SetMinSize(*args, **kwargs)
1377
1378 #------------------------------------------------------------
1380 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1381 return super(cReportListCtrl, self).SetSize(*args, **kwargs)
1382
1383 #------------------------------------------------------------
1385 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1386 return super(cReportListCtrl, self).SetSizeHints(*args, **kwargs)
1387
1388 #------------------------------------------------------------
1390 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1391 return super(cReportListCtrl, self).SetSizeHintsSz(*args, **kwargs)
1392
1393 #------------------------------------------------------------
1395 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1396 return super(cReportListCtrl, self).SetSizeWH(*args, **kwargs)
1397
1398 #------------------------------------------------------------
1400 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1401 return super(cReportListCtrl, self).SetVirtualSize(*args, **kwargs)
1402
1403 #------------------------------------------------------------
1405 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1406 return super(cReportListCtrl, self).SetVirtualSizeHints(self, *args, **kwargs)
1407
1408 #------------------------------------------------------------
1410 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1411 return super(cReportListCtrl, self).SetVirtualSizeHintsSz(*args, **kwargs)
1412
1413 #------------------------------------------------------------
1415 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1416 return super(cReportListCtrl, self).SetVirtualSizeWH(*args, **kwargs)
1417
1418 #------------------------------------------------------------
1420 res = super(cReportListCtrl, self).GetAdjustedBestSize(*args, **kwargs)
1421 kwargs['sizing_function_result'] = res
1422 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1423 return res
1424
1425 #------------------------------------------------------------
1427 res = super(cReportListCtrl, self).GetEffectiveMinSize(*args, **kwargs)
1428 kwargs['sizing_function_result'] = res
1429 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1430 return res
1431
1432 #------------------------------------------------------------
1434 res = super(cReportListCtrl, self).GetBestSize(*args, **kwargs)
1435 kwargs['sizing_function_result'] = res
1436 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1437 return res
1438
1439 #------------------------------------------------------------
1441 res = super(cReportListCtrl, self).GetBestSizeTuple(*args, **kwargs)
1442 kwargs['sizing_function_result'] = res
1443 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1444 return res
1445
1446 #------------------------------------------------------------
1448 res = super(cReportListCtrl, self).GetBestVirtualSize(*args, **kwargs)
1449 kwargs['sizing_function_result'] = res
1450 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1451 return res
1452
1453 #------------------------------------------------------------
1455 res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
1456 kwargs['sizing_function_result'] = res
1457 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1458 return res
1459
1460 #------------------------------------------------------------
1462 res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
1463 kwargs['sizing_function_result'] = res
1464 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1465 return res
1466
1467 #------------------------------------------------------------
1469 res = super(cReportListCtrl, self).GetMaxClientSize(*args, **kwargs)
1470 kwargs['sizing_function_result'] = res
1471 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1472 return res
1473
1474 #------------------------------------------------------------
1476 res = super(cReportListCtrl, self).GetMaxHeight(*args, **kwargs)
1477 kwargs['sizing_function_result'] = res
1478 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1479 return res
1480
1481 #------------------------------------------------------------
1483 res = super(cReportListCtrl, self).GetMaxSize(*args, **kwargs)
1484 kwargs['sizing_function_result'] = res
1485 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1486 return res
1487
1488 #------------------------------------------------------------
1490 res = super(cReportListCtrl, self).GetMaxWidth(*args, **kwargs)
1491 kwargs['sizing_function_result'] = res
1492 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1493 return res
1494
1495 #------------------------------------------------------------
1497 res = super(cReportListCtrl, self).GetMinClientSize(*args, **kwargs)
1498 kwargs['sizing_function_result'] = res
1499 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1500 return res
1501
1502 #------------------------------------------------------------
1504 res = super(cReportListCtrl, self).GetMinHeight(*args, **kwargs)
1505 kwargs['sizing_function_result'] = res
1506 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1507 return res
1508
1509 #------------------------------------------------------------
1511 res = super(cReportListCtrl, self).GetMinSize(*args, **kwargs)
1512 kwargs['sizing_function_result'] = res
1513 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1514 return res
1515
1516 #------------------------------------------------------------
1518 res = super(cReportListCtrl, self).GetMinWidth(*args, **kwargs)
1519 kwargs['sizing_function_result'] = res
1520 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1521 return res
1522
1523 #------------------------------------------------------------
1525 res = super(cReportListCtrl, self).GetSize(*args, **kwargs)
1526 kwargs['sizing_function_result'] = res
1527 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1528 return res
1529
1530 #------------------------------------------------------------
1532 res = super(cReportListCtrl, self).GetVirtualSize(*args, **kwargs)
1533 kwargs['sizing_function_result'] = res
1534 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1535 return res
1536
1537 #------------------------------------------------------------
1539 res = super(cReportListCtrl, self).GetVirtualSizeTuple(*args, **kwargs)
1540 kwargs['sizing_function_result'] = res
1541 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1542 return res
1543
1544 #------------------------------------------------------------
1545 # setters
1546 #------------------------------------------------------------
1548 """(Re)define the columns.
1549
1550 Note that this will (have to) delete the items.
1551 """
1552 self.ClearAll()
1553 self.__tt_last_item = None
1554 if columns is None:
1555 return
1556 for idx in range(len(columns)):
1557 self.InsertColumn(idx, columns[idx])
1558
1559 listmixins.ColumnSorterMixin.__init__(self, 0)
1560 self._invalidate_sorting_metadata()
1561
1562 #------------------------------------------------------------
1564 """Set the column width policy.
1565
1566 widths = None:
1567 use previous policy if any or default policy
1568 widths != None:
1569 use this policy and remember it for later calls
1570
1571 options:
1572 wx.LIST_AUTOSIZE_USEHEADER
1573 wx.LIST_AUTOSIZE
1574
1575 This means there is no way to *revert* to the default policy :-(
1576 """
1577 # explicit policy ?
1578 if widths is not None:
1579 self.__widths = widths
1580 for idx in range(len(self.__widths)):
1581 self.SetColumnWidth(idx, self.__widths[idx])
1582 return
1583
1584 # previous policy ?
1585 if self.__widths is not None:
1586 for idx in range(len(self.__widths)):
1587 self.SetColumnWidth(idx, self.__widths[idx])
1588 return
1589
1590 # default policy !
1591 if self.GetItemCount() == 0:
1592 width_type = wx.LIST_AUTOSIZE_USEHEADER
1593 else:
1594 width_type = wx.LIST_AUTOSIZE
1595 for idx in range(self.GetColumnCount()):
1596 self.SetColumnWidth(idx, width_type)
1597
1598 #------------------------------------------------------------
1600 if column != 'LAST':
1601 if column > self.ColumnCount:
1602 return
1603 # this column will take up all remaining space courtesy of the width mixin
1604 self.setResizeColumn(column)
1605
1606 #------------------------------------------------------------
1608 assert(col_idx > -1), '<col_idx> must be positive integer'
1609 if col_idx > self.ColumnCount:
1610 _log.warning('<col_idx>=%s, .ColumnCount=%s', col_idx, self.ColumnCount)
1611 return
1612
1613 sort_col_idx, is_ascending = self.GetSortState()
1614 col_state = self.GetColumn(col_idx)
1615 col_state.Text = label
1616 if col_idx == sort_col_idx:
1617 col_state.Text += self.sort_order_tags[is_ascending]
1618 self.SetColumn(col_idx, col_state)
1619
1620 #------------------------------------------------------------
1622 tries = 0
1623 while tries < max_tries:
1624 if self.debug is not None:
1625 if self.debug.endswith('_deleting'):
1626 _log.debug('[round %s] <%s>.GetItemCount() before DeleteAllItems(): %s (thread [%s])', tries, self.debug, self.GetItemCount(), threading.get_ident())
1627 if not self.DeleteAllItems():
1628 _log.error('<%s>.DeleteAllItems() failed', self.debug)
1629 item_count = self.GetItemCount()
1630 if item_count == 0:
1631 return True
1632 wx.SafeYield(None, True)
1633 _log.error('<%s>.GetItemCount() not 0 (rather: %s) after DeleteAllItems()', self.debug, item_count)
1634 time.sleep(0.3)
1635 wx.SafeYield(None, True)
1636 tries += 1
1637
1638 _log.error('<%s>: unable to delete list items after looping %s times', self.debug, max_tries)
1639 return False
1640
1641 #------------------------------------------------------------
1643 """All item members must be str()able or None."""
1644
1645 wx.BeginBusyCursor()
1646 self._invalidate_sorting_metadata()
1647
1648 if self.ItemCount == 0:
1649 topmost_visible = 0
1650 else:
1651 topmost_visible = self.GetFirstSelected()
1652 if topmost_visible == -1:
1653 topmost_visible = self.GetFocusedItem()
1654 if topmost_visible == -1:
1655 topmost_visible = self.TopItem
1656
1657 if not self.remove_items_safely(max_tries = 3):
1658 _log.error("cannot remove items (!?), continuing and hoping for the best")
1659
1660 if items is None:
1661 self.data = None
1662 wx.EndBusyCursor()
1663 return
1664
1665 # insert new items
1666 for item in items:
1667 # item is a single string
1668 # (typical special case: items=rows are a list-of-strings)
1669 if isinstance(item, str):
1670 self.InsertItem(index = sys.maxsize, label = item.replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] '))
1671 continue
1672 # item is something else, either ...
1673 try:
1674 # ... an iterable
1675 col_val = str(item[0])
1676 row_num = self.InsertItem(index = sys.maxsize, label = col_val)
1677 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1678 col_val = str(item[col_num]).replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')
1679 self.SetItem(index = row_num, column = col_num, label = col_val)
1680 except (TypeError, KeyError, IndexError):
1681 # ... an *empty* iterable [IndexError]
1682 # ... or not iterable (None, int, instance, dict [KeyError] ...)
1683 col_val = str(item).replace('\r\n', ' [CRLF] ').replace('\n', ' [LF] ')
1684 self.InsertItem(index = sys.maxsize, label = col_val)
1685
1686 if reshow:
1687 if self.ItemCount > 0:
1688 if topmost_visible < self.ItemCount:
1689 self.EnsureVisible(topmost_visible)
1690 self.Focus(topmost_visible)
1691 else:
1692 self.EnsureVisible(self.ItemCount - 1)
1693 self.Focus(self.ItemCount - 1)
1694
1695 # set data to be a copy of items
1696 self.data = items
1697
1698 wx.EndBusyCursor()
1699
1700 #--------------------------
1702 if self.ItemCount == 0:
1703 return []
1704
1705 rows = []
1706 for row_idx in range(self.ItemCount):
1707 row = []
1708 for col_idx in range(self.ColumnCount):
1709 row.append(self.GetItem(row_idx, col_idx).GetText())
1710 rows.append(row)
1711 return rows
1712
1713 # old: only returned first column
1714 #return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
1715
1716 string_items = property(get_string_items, set_string_items)
1717
1718 #------------------------------------------------------------
1720 if len(new_items) == 0:
1721 return
1722
1723 if new_data is None:
1724 new_data = new_items
1725
1726 existing_data = self.get_item_data()
1727 if existing_data is None:
1728 existing_data = []
1729
1730 if allow_dupes:
1731 self.set_string_items (
1732 items = self.string_items.extend(new_items),
1733 reshow = True
1734 )
1735 self.data = existing_data.extend(new_data)
1736 self.set_column_widths()
1737 return
1738
1739 existing_items = self.get_string_items()
1740 for new_item, new_data in zip(new_items, new_data):
1741 if new_item in existing_items:
1742 continue
1743 existing_items.append(new_item)
1744 existing_data.append(new_data)
1745 self.set_string_items (
1746 items = existing_items,
1747 reshow = True
1748 )
1749 self.data = existing_data
1750 self.set_column_widths()
1751
1752 #------------------------------------------------------------
1754 """<data> assumed to be a list corresponding to the item indices"""
1755 if data is not None:
1756 item_count = self.GetItemCount()
1757 if len(data) != item_count:
1758 _log.debug('<data> length (%s) must be equal to number of list items (%s) (%s, thread [%s])', len(data), item_count, self.debug, threading.get_ident())
1759 for item_idx in range(len(data)):
1760 self.SetItemData(item_idx, item_idx)
1761 self.__data = data
1762 self.__tt_last_item = None
1763 # string data (rows/visible list items) not modified,
1764 # so no need to call _update_sorting_metadata
1765 return
1766
1768 # slower than "return self.__data" but helps with detecting
1769 # problems with len(__data) != self.GetItemCount(),
1770 # also takes care of returning data in the order corresponding
1771 # to the order get_string_items returns rows
1772 return self.get_item_data() # returns all data since item_idx is None
1773
1774 data = property(_get_data, set_data)
1775
1776 #------------------------------------------------------------
1778 # not sure why this is done:
1779 if self.GetItemCount() > 0:
1780 self.Select(0, on = 0)
1781 if selections is None:
1782 return
1783 for idx in selections:
1784 self.Select(idx = idx, on = 1)
1785
1787 if self.ItemCount == 0:
1788 return []
1789 if self.__is_single_selection:
1790 return [self.GetFirstSelected()]
1791 selections = []
1792 idx = self.GetFirstSelected()
1793 while idx != -1:
1794 selections.append(idx)
1795 idx = self.GetNextSelected(idx)
1796 return selections
1797
1798 selections = property(__get_selections, set_selections)
1799
1800 #------------------------------------------------------------
1801 # getters
1802 #------------------------------------------------------------
1804 labels = []
1805 for col_idx in range(self.ColumnCount):
1806 col = self.GetColumn(col = col_idx)
1807 labels.append(col.Text)
1808 return labels
1809
1810 column_labels = property(get_column_labels, lambda x:x)
1811
1812 #------------------------------------------------------------
1814 if self.ItemCount == 0:
1815 _log.warning('no items')
1816 return None
1817 if item_idx is not None:
1818 return self.GetItem(item_idx)
1819 _log.error('get_item(None) called')
1820 return None
1821
1822 #------------------------------------------------------------
1824 if self.ItemCount == 0:
1825 return []
1826 return [ self.GetItem(item_idx) for item_idx in range(self.ItemCount) ]
1827
1828 items = property(get_items, lambda x:x)
1829
1830 #------------------------------------------------------------
1832
1833 if self.ItemCount == 0:
1834 if self.__is_single_selection or only_one:
1835 return None
1836 return []
1837
1838 if self.__is_single_selection or only_one:
1839 return self.GetFirstSelected()
1840
1841 items = []
1842 idx = self.GetFirstSelected()
1843 while idx != -1:
1844 items.append(idx)
1845 idx = self.GetNextSelected(idx)
1846
1847 return items
1848
1849 selected_items = property(get_selected_items, lambda x:x)
1850
1851 #------------------------------------------------------------
1853
1854 if self.ItemCount == 0:
1855 if self.__is_single_selection or only_one:
1856 return None
1857 return []
1858
1859 if self.__is_single_selection or only_one:
1860 return self.GetItemText(self.GetFirstSelected())
1861
1862 items = []
1863 idx = self.GetFirstSelected()
1864 while idx != -1:
1865 items.append(self.GetItemText(idx))
1866 idx = self.GetNextSelected(idx)
1867
1868 return items
1869
1870 selected_string_items = property(get_selected_string_items, lambda x:x)
1871
1872 #------------------------------------------------------------
1874
1875 if self.__data is None: # this isn't entirely clean
1876 return None
1877
1878 if item_idx is not None:
1879 return self.__data[self.map_item_idx2data_idx(item_idx)]
1880
1881 # if <idx> is None return all data up to item_count,
1882 # in case of len(__data) != self.GetItemCount() this
1883 # gives the chance to figure out what is going on
1884 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1885
1886 item_data = property(get_item_data, lambda x:x)
1887
1888 #------------------------------------------------------------
1890
1891 if self.__is_single_selection or only_one:
1892 if self.__data is None:
1893 return None
1894 idx = self.GetFirstSelected()
1895 if idx == -1:
1896 return None
1897 return self.__data[self.map_item_idx2data_idx(idx)]
1898
1899 data = []
1900 if self.__data is None:
1901 return data
1902 idx = self.GetFirstSelected()
1903 while idx != -1:
1904 data.append(self.__data[self.map_item_idx2data_idx(idx)])
1905 idx = self.GetNextSelected(idx)
1906
1907 return data
1908
1909 selected_item_data = property(get_selected_item_data, lambda x:x)
1910
1911 #------------------------------------------------------------
1913 self.Select(idx = self.GetFirstSelected(), on = 0)
1914
1915 #------------------------------------------------------------
1917 # do NOT remove the corresponding data because even if
1918 # the item pointing to this data instance is gone all
1919 # other items will still point to their corresponding
1920 # *initial* row numbers
1921 #if self.__data is not None:
1922 # del self.__data[self.map_item_idx2data_idx(item_idx)]
1923 self.DeleteItem(item_idx)
1924 self.__tt_last_item = None
1925 self._invalidate_sorting_metadata()
1926
1927 #------------------------------------------------------------
1928 # internal helpers
1929 #------------------------------------------------------------
2158
2159 #------------------------------------------------------------
2161 if self.__delete_callback is None:
2162 return
2163
2164 no_items = len(self.get_selected_items(only_one = False))
2165 if no_items == 0:
2166 return
2167
2168 if no_items > 1:
2169 question = _(
2170 '%s list items are selected.\n'
2171 '\n'
2172 'Really delete all %s items ?'
2173 ) % (no_items, no_items)
2174 title = _('Deleting list items')
2175 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
2176 dlg = wx.MessageDialog(None, question, title, style)
2177 btn_pressed = dlg.ShowModal()
2178 dlg.DestroyLater()
2179 if btn_pressed == wx.ID_NO:
2180 self.SetFocus()
2181 return
2182 if btn_pressed == wx.ID_CANCEL:
2183 self.SetFocus()
2184 return
2185
2186 self.__delete_callback()
2187 return
2188
2189 #------------------------------------------------------------
2194
2195 #------------------------------------------------------------
2200
2201 #------------------------------------------------------------
2203 #print "showing search dlg"
2204 if self.__search_term is None:
2205 #print "no prev search term"
2206 default = ''
2207 else:
2208 #print "prev search term:", self.__search_term
2209 default = self.__search_term
2210 search_term = wx.GetTextFromUser (
2211 _('Enter the search term:'),
2212 _('List search'),
2213 default_value = default
2214 )
2215 if search_term.strip() == '':
2216 #print "empty search term"
2217 self.__search_term = None
2218 self.__tt_static_part = self.__tt_static_part_base
2219 return
2220
2221 #print "search term:", search_term
2222 self.__search_term = search_term
2223 self.__tt_static_part = _(
2224 'Current search term: [[%s]]\n'
2225 '\n'
2226 '%s'
2227 ) % (
2228 search_term,
2229 self.__tt_static_part_base
2230 )
2231 self.__search_match()
2232
2233 #------------------------------------------------------------
2234 # event handlers
2235 #------------------------------------------------------------
2237 event.Skip()
2238 if self.__activate_callback is not None:
2239 self.__activate_callback(event)
2240 return
2241 # default double-click / ENTER action: edit
2242 self.__handle_edit()
2243
2244 #------------------------------------------------------------
2246 if self.__select_callback is not None:
2247 self.__select_callback(event)
2248 else:
2249 event.Skip()
2250
2251 #------------------------------------------------------------
2253 if self.__deselect_callback is not None:
2254 self.__deselect_callback(event)
2255 else:
2256 event.Skip()
2257
2258 #------------------------------------------------------------
2262
2263 #------------------------------------------------------------
2265 evt.Skip()
2266
2267 if evt.KeyCode == wx.WXK_DELETE:
2268 self.__handle_delete()
2269 return
2270
2271 if evt.KeyCode == wx.WXK_INSERT:
2272 self.__handle_insert()
2273 return
2274
2275 if evt.KeyCode == wx.WXK_MENU:
2276 self.__show_context_menu(evt.Index)
2277 return
2278
2279 #------------------------------------------------------------
2281
2282 if chr(evt.GetRawKeyCode()) == 'f':
2283 if evt.GetModifiers() == wx.MOD_CMD:
2284 #print "search dialog invoked"
2285 self.__show_search_dialog()
2286 return
2287
2288 if chr(evt.GetRawKeyCode()) == 'n':
2289 if evt.GetModifiers() == wx.MOD_CMD:
2290 #print "search-next key invoked"
2291 self.__search_match()
2292 return
2293
2294 evt.Skip()
2295 return
2296
2297 #------------------------------------------------------------
2299 """Update tooltip on mouse motion.
2300
2301 for s in dir(wx):
2302 if s.startswith('LIST_HITTEST'):
2303 print s, getattr(wx, s)
2304
2305 LIST_HITTEST_ABOVE 1
2306 LIST_HITTEST_BELOW 2
2307 LIST_HITTEST_NOWHERE 4
2308 LIST_HITTEST_ONITEM 672
2309 LIST_HITTEST_ONITEMICON 32
2310 LIST_HITTEST_ONITEMLABEL 128
2311 LIST_HITTEST_ONITEMRIGHT 256
2312 LIST_HITTEST_ONITEMSTATEICON 512
2313 LIST_HITTEST_TOLEFT 1024
2314 LIST_HITTEST_TORIGHT 2048
2315 """
2316 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
2317
2318 # pointer on item related area at all ?
2319 if where_flag not in [
2320 wx.LIST_HITTEST_ONITEMLABEL,
2321 wx.LIST_HITTEST_ONITEMICON,
2322 wx.LIST_HITTEST_ONITEMSTATEICON,
2323 wx.LIST_HITTEST_ONITEMRIGHT,
2324 wx.LIST_HITTEST_ONITEM
2325 ]:
2326 self.__tt_last_item = None # not on any item
2327 self.SetToolTip(self.__tt_static_part)
2328 return
2329
2330 # same item as last time around ?
2331 if self.__tt_last_item == item_idx:
2332 return
2333
2334 # remeber the new item we are on
2335 self.__tt_last_item = item_idx
2336
2337 # HitTest() can return -1 if it so pleases, meaning that no item
2338 # was hit or else that maybe there aren't any items (empty list)
2339 if item_idx == wx.NOT_FOUND:
2340 self.SetToolTip(self.__tt_static_part)
2341 return
2342
2343 # do we *have* item data ?
2344 if self.__data is None:
2345 self.SetToolTip(self.__tt_static_part)
2346 return
2347
2348 # under some circumstances the item_idx returned
2349 # by HitTest() may be out of bounds with respect to
2350 # self.__data, this hints at a sync problem between
2351 # setting display items and associated data
2352 if (
2353 (item_idx > (len(self.__data) - 1))
2354 or
2355 (item_idx < -1)
2356 ):
2357 self.SetToolTip(self.__tt_static_part)
2358 print("*************************************************************")
2359 print("GNUmed has detected an inconsistency with list item tooltips.")
2360 print("")
2361 print("This is not a big problem and you can keep working.")
2362 print("")
2363 print("However, please send us the following so we can fix GNUmed:")
2364 print("")
2365 print("item idx: %s" % item_idx)
2366 print('where flag: %s' % where_flag)
2367 print('data list length: %s' % len(self.__data))
2368 print("*************************************************************")
2369 return
2370
2371 dyna_tt = None
2372 if self.__item_tooltip_callback is not None:
2373 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
2374
2375 if dyna_tt is None:
2376 self.SetToolTip(self.__tt_static_part)
2377 return
2378
2379 self.SetToolTip(dyna_tt)
2380
2381 #------------------------------------------------------------
2382 # context menu event hendlers
2383 #------------------------------------------------------------
2387
2388 #------------------------------------------------------------
2392
2393 #------------------------------------------------------------
2397
2398 #------------------------------------------------------------
2402
2403 #------------------------------------------------------------
2407
2408 #------------------------------------------------------------
2410
2411 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2412 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2413
2414 col_labels = self.column_labels
2415 line = '%s' % ' || '.join(col_labels)
2416 txt_file.write('%s\n' % line)
2417 txt_file.write(('=' * len(line)) + '\n')
2418
2419 for item_idx in range(self.ItemCount):
2420 fields = []
2421 for col_idx in range(self.ColumnCount):
2422 fields.append(self.GetItem(item_idx, col_idx).Text)
2423 txt_file.write('%s\n' % ' || '.join(fields))
2424
2425 txt_file.close()
2426 gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % txt_name)
2427
2428 #------------------------------------------------------------
2430
2431 csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2432 csv_file = io.open(csv_name, mode = 'wt', encoding = 'utf8')
2433 csv_writer = csv.writer(csv_file)
2434 csv_writer.writerow([ l for l in self.column_labels ])
2435 for item_idx in range(self.ItemCount):
2436 fields = []
2437 for col_idx in range(self.ColumnCount):
2438 fields.append(self.GetItem(item_idx, col_idx).Text)
2439 csv_writer.writerow([ f for f in fields ])
2440 csv_file.close()
2441 gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % csv_name)
2442
2443 #------------------------------------------------------------
2445
2446 if (self.__data is None) or (self.__item_tooltip_callback is None):
2447 return
2448
2449 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2450 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2451
2452 for data in self.data:
2453 tt = self.__item_tooltip_callback(data)
2454 if tt is None:
2455 continue
2456 txt_file.write('%s\n\n' % tt)
2457
2458 txt_file.close()
2459 gmDispatcher.send(signal = 'statustext', msg = _('All tooltips saved to [%s].') % txt_name)
2460
2461 #------------------------------------------------------------
2463
2464 if self.__data is None:
2465 return
2466
2467 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2468 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2469
2470 for data in self.data:
2471 if hasattr(data, 'format'):
2472 txt = data.format()
2473 if type(txt) is list:
2474 txt = '\n'.join(txt)
2475 else:
2476 txt = '%s' % data
2477 txt_file.write('%s\n\n' % txt)
2478
2479 txt_file.close()
2480 gmDispatcher.send(signal = 'statustext', msg = _('All data saved to [%s].') % txt_name)
2481
2482 #------------------------------------------------------------
2484
2485 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2486 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2487
2488 col_labels = self.column_labels
2489 line = '%s' % ' || '.join(col_labels)
2490 txt_file.write('%s\n' % line)
2491 txt_file.write(('=' * len(line)) + '\n')
2492
2493 items = self.selected_items
2494 if self.__is_single_selection:
2495 items = [items]
2496
2497 for item_idx in items:
2498 fields = []
2499 for col_idx in range(self.ColumnCount):
2500 fields.append(self.GetItem(item_idx, col_idx).Text)
2501 txt_file.write('%s\n' % ' || '.join(fields))
2502
2503 txt_file.close()
2504 gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % txt_name)
2505
2506 #------------------------------------------------------------
2508
2509 csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2510 csv_file = io.open(csv_name, mode = 'wt', encoding = 'utf8')
2511 csv_writer = csv.writer(csv_file)
2512 csv_writer.writerow([ l for l in self.column_labels ])
2513 items = self.selected_items
2514 if self.__is_single_selection:
2515 items = [items]
2516 for item_idx in items:
2517 fields = []
2518 for col_idx in range(self.ColumnCount):
2519 fields.append(self.GetItem(item_idx, col_idx).Text)
2520 csv_writer.writerow([ f for f in fields ])
2521 csv_file.close()
2522 gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % csv_name)
2523
2524 #------------------------------------------------------------
2526
2527 if (self.__data is None) or (self.__item_tooltip_callback is None):
2528 return
2529
2530 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2531 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2532
2533 for data in self.selected_item_data:
2534 tt = self.__item_tooltip_callback(data)
2535 if tt is None:
2536 continue
2537 txt_file.write('%s\n\n' % tt)
2538
2539 txt_file.close()
2540 gmDispatcher.send(signal = 'statustext', msg = _('Selected tooltips saved to [%s].') % txt_name)
2541
2542 #------------------------------------------------------------
2544
2545 if self.__data is None:
2546 return
2547
2548 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2549 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2550
2551 for data in self.selected_item_data:
2552 if hasattr(data, 'format'):
2553 txt = data.format()
2554 if type(txt) is list:
2555 txt = '\n'.join(txt)
2556 else:
2557 txt = '%s' % data
2558 txt_file.write('%s\n\n' % txt)
2559
2560 txt_file.close()
2561 gmDispatcher.send(signal = 'statustext', msg = _('Selected data saved to [%s].') % txt_name)
2562
2563 #------------------------------------------------------------
2565 dlg = self.containing_dlg
2566 if dlg is None:
2567 widget2screenshot = self
2568 else:
2569 widget2screenshot = dlg
2570 png_name = os.path.join (
2571 gmTools.gmPaths().home_dir,
2572 'gnumed',
2573 'gm-%s-%s.png' % (self.useful_title, pydt.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
2574 )
2575 from Gnumed.wxpython.gmGuiHelpers import save_screenshot_to_file
2576 save_screenshot_to_file(filename = png_name, widget = widget2screenshot, settle_time = 500)
2577
2578 #------------------------------------------------------------
2580 dlg = self.containing_dlg
2581 if dlg is None:
2582 widget2screenshot = self
2583 else:
2584 widget2screenshot = dlg
2585 png_name = os.path.join (
2586 gmTools.gmPaths().home_dir,
2587 'gnumed',
2588 'gm-%s-%s.png' % (self.useful_title, pydt.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'))
2589 )
2590 from Gnumed.wxpython.gmGuiHelpers import save_screenshot_to_file
2591 screenshot_file = save_screenshot_to_file(widget = widget2screenshot, settle_time = 500)
2592 gmDispatcher.send(signal = 'add_file_to_export_area', filename = screenshot_file, hint = _('GMd screenshot'))
2593
2594 #------------------------------------------------------------
2596 if wx.TheClipboard.IsOpened():
2597 _log.debug('clipboard already open')
2598 return
2599 if not wx.TheClipboard.Open():
2600 _log.debug('cannot open clipboard')
2601 return
2602 data_obj = wx.TextDataObject()
2603
2604 if (self.__data is None) or (self.__item_tooltip_callback is None):
2605 txt = self.__tt_static_part
2606 else:
2607 txt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)])
2608 if txt is None:
2609 txt = self.__tt_static_part
2610
2611 data_obj.SetText(txt)
2612 wx.TheClipboard.SetData(data_obj)
2613 wx.TheClipboard.Close()
2614
2615 #------------------------------------------------------------
2617 if wx.TheClipboard.IsOpened():
2618 _log.debug('clipboard already open')
2619 return
2620 if not wx.TheClipboard.Open():
2621 _log.debug('cannot open clipboard')
2622 return
2623
2624 if (self.__data is None) or (self.__item_tooltip_callback is None):
2625 return
2626
2627 tts = []
2628 for data in self.selected_item_data:
2629 tt = self.__item_tooltip_callback(data)
2630 if tt is None:
2631 continue
2632 tts.append(tt)
2633
2634 if len(tts) == 0:
2635 return
2636
2637 data_obj = wx.TextDataObject()
2638 data_obj.SetText('\n\n'.join(tts))
2639 wx.TheClipboard.SetData(data_obj)
2640 wx.TheClipboard.Close()
2641
2642 #------------------------------------------------------------
2644 if wx.TheClipboard.IsOpened():
2645 _log.debug('clipboard already open')
2646 return
2647 if not wx.TheClipboard.Open():
2648 _log.debug('cannot open clipboard')
2649 return
2650 data_obj = wx.TextDataObject()
2651
2652 txt = ''
2653 # get previous text
2654 got_it = wx.TheClipboard.GetData(data_obj)
2655 if got_it:
2656 txt = data_obj.Text + '\n'
2657
2658 # add text
2659 if (self.__data is None) or (self.__item_tooltip_callback is None):
2660 txt += self.__tt_static_part
2661 else:
2662 tmp = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)])
2663 if tmp is None:
2664 txt += self.__tt_static_part
2665 else:
2666 txt += tmp
2667
2668 # set text
2669 data_obj.SetText(txt)
2670 wx.TheClipboard.SetData(data_obj)
2671 wx.TheClipboard.Close()
2672
2673 #------------------------------------------------------------
2675 if wx.TheClipboard.IsOpened():
2676 _log.debug('clipboard already open')
2677 return
2678 if not wx.TheClipboard.Open():
2679 _log.debug('cannot open clipboard')
2680 return
2681
2682 if (self.__data is None) or (self.__item_tooltip_callback is None):
2683 return
2684
2685 tts = []
2686 for data in self.selected_item_data:
2687 tt = self.__item_tooltip_callback(data)
2688 if tt is None:
2689 continue
2690 tts.append(tt)
2691
2692 if len(tts) == 0:
2693 return
2694
2695 data_obj = wx.TextDataObject()
2696 txt = ''
2697 # get previous text
2698 got_it = wx.TheClipboard.GetData(data_obj)
2699 if got_it:
2700 txt = data_obj.Text + '\n\n'
2701 txt += '\n\n'.join(tts)
2702
2703 data_obj.SetText(txt)
2704 wx.TheClipboard.SetData(data_obj)
2705 wx.TheClipboard.Close()
2706
2707 #------------------------------------------------------------
2709 if wx.TheClipboard.IsOpened():
2710 _log.debug('clipboard already open')
2711 return
2712 if not wx.TheClipboard.Open():
2713 _log.debug('cannot open clipboard')
2714 return
2715 data_obj = wx.TextDataObject()
2716 data_obj.SetText(' // '.join(self._rclicked_row_cells))
2717 wx.TheClipboard.SetData(data_obj)
2718 wx.TheClipboard.Close()
2719
2720 #------------------------------------------------------------
2722 if wx.TheClipboard.IsOpened():
2723 _log.debug('clipboard already open')
2724 return
2725 if not wx.TheClipboard.Open():
2726 _log.debug('cannot open clipboard')
2727 return
2728
2729 rows = []
2730 for item_idx in self.selected_items:
2731 rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
2732
2733 data_obj = wx.TextDataObject()
2734 data_obj.SetText('\n\n'.join(rows))
2735 wx.TheClipboard.SetData(data_obj)
2736 wx.TheClipboard.Close()
2737
2738 #------------------------------------------------------------
2740 if wx.TheClipboard.IsOpened():
2741 _log.debug('clipboard already open')
2742 return
2743 if not wx.TheClipboard.Open():
2744 _log.debug('cannot open clipboard')
2745 return
2746 data_obj = wx.TextDataObject()
2747
2748 txt = ''
2749 # get previous text
2750 got_it = wx.TheClipboard.GetData(data_obj)
2751 if got_it:
2752 txt = data_obj.Text + '\n'
2753
2754 # add text
2755 txt += ' // '.join(self._rclicked_row_cells)
2756
2757 # set text
2758 data_obj.SetText(txt)
2759 wx.TheClipboard.SetData(data_obj)
2760 wx.TheClipboard.Close()
2761
2762 #------------------------------------------------------------
2764 if wx.TheClipboard.IsOpened():
2765 _log.debug('clipboard already open')
2766 return
2767 if not wx.TheClipboard.Open():
2768 _log.debug('cannot open clipboard')
2769 return
2770
2771 rows = []
2772 for item_idx in self.selected_items:
2773 rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
2774
2775 data_obj = wx.TextDataObject()
2776
2777 txt = ''
2778 # get previous text
2779 got_it = wx.TheClipboard.GetData(data_obj)
2780 if got_it:
2781 txt = data_obj.Text + '\n'
2782 txt += '\n\n'.join(rows)
2783
2784 data_obj.SetText(txt)
2785 wx.TheClipboard.SetData(data_obj)
2786 wx.TheClipboard.Close()
2787
2788 #------------------------------------------------------------
2790 if wx.TheClipboard.IsOpened():
2791 _log.debug('clipboard already open')
2792 return
2793 if not wx.TheClipboard.Open():
2794 _log.debug('cannot open clipboard')
2795 return
2796 data_obj = wx.TextDataObject()
2797 data_obj.SetText('\n'.join(self._rclicked_row_cells_w_hdr))
2798 wx.TheClipboard.SetData(data_obj)
2799 wx.TheClipboard.Close()
2800
2801 #------------------------------------------------------------
2803 if wx.TheClipboard.IsOpened():
2804 _log.debug('clipboard already open')
2805 return
2806 if not wx.TheClipboard.Open():
2807 _log.debug('cannot open clipboard')
2808 return
2809 data_obj = wx.TextDataObject()
2810
2811 txt = ''
2812 # get previous text
2813 got_it = wx.TheClipboard.GetData(data_obj)
2814 if got_it:
2815 txt = data_obj.Text + '\n'
2816
2817 # add text
2818 txt += '\n'.join(self._rclicked_row_cells_w_hdr)
2819
2820 # set text
2821 data_obj.SetText(txt)
2822 wx.TheClipboard.SetData(data_obj)
2823 wx.TheClipboard.Close()
2824
2825 #------------------------------------------------------------
2827 if wx.TheClipboard.IsOpened():
2828 _log.debug('clipboard already open')
2829 return
2830 if not wx.TheClipboard.Open():
2831 _log.debug('cannot open clipboard')
2832 return
2833 data_obj = wx.TextDataObject()
2834 txt = self._rclicked_row_data.format()
2835 if type(txt) == type([]):
2836 txt = '\n'.join(txt)
2837 data_obj.SetText(txt)
2838 wx.TheClipboard.SetData(data_obj)
2839 wx.TheClipboard.Close()
2840
2841 #------------------------------------------------------------
2843 if wx.TheClipboard.IsOpened():
2844 _log.debug('clipboard already open')
2845 return
2846 if not wx.TheClipboard.Open():
2847 _log.debug('cannot open clipboard')
2848 return
2849
2850 data_as_txt = []
2851 for data in self.selected_item_data:
2852 if hasattr(data, 'format'):
2853 txt = data.format()
2854 if type(txt) is list:
2855 txt = '\n'.join(txt)
2856 else:
2857 txt = '%s' % data
2858 data_as_txt.append(txt)
2859
2860 data_obj = wx.TextDataObject()
2861 data_obj.SetText('\n\n'.join(data_as_txt))
2862 wx.TheClipboard.SetData(data_obj)
2863 wx.TheClipboard.Close()
2864
2865 #------------------------------------------------------------
2867 if wx.TheClipboard.IsOpened():
2868 _log.debug('clipboard already open')
2869 return
2870 if not wx.TheClipboard.Open():
2871 _log.debug('cannot open clipboard')
2872 return
2873 data_obj = wx.TextDataObject()
2874
2875 txt = ''
2876 # get previous text
2877 got_it = wx.TheClipboard.GetData(data_obj)
2878 if got_it:
2879 txt = data_obj.Text + '\n'
2880
2881 # add text
2882 tmp = self._rclicked_row_data.format()
2883 if type(tmp) == type([]):
2884 txt += '\n'.join(tmp)
2885 else:
2886 txt += tmp
2887
2888 # set text
2889 data_obj.SetText(txt)
2890 wx.TheClipboard.SetData(data_obj)
2891 wx.TheClipboard.Close()
2892
2893 #------------------------------------------------------------
2895 if wx.TheClipboard.IsOpened():
2896 _log.debug('clipboard already open')
2897 return
2898 if not wx.TheClipboard.Open():
2899 _log.debug('cannot open clipboard')
2900 return
2901
2902 data_as_txt = []
2903 for data in self.selected_item_data:
2904 if hasattr(data, 'format'):
2905 txt = data.format()
2906 if type(txt) is list:
2907 txt = '\n'.join(txt)
2908 else:
2909 txt = '%s' % data
2910 data_as_txt.append(txt)
2911
2912 data_obj = wx.TextDataObject()
2913 txt = ''
2914 # get previous text
2915 got_it = wx.TheClipboard.GetData(data_obj)
2916 if got_it:
2917 txt = data_obj.Text + '\n'
2918 txt += '\n'.join(data_as_txt)
2919
2920 # set text
2921 data_obj.SetText(txt)
2922 wx.TheClipboard.SetData(data_obj)
2923 wx.TheClipboard.Close()
2924
2925 #------------------------------------------------------------
2927 if wx.TheClipboard.IsOpened():
2928 _log.debug('clipboard already open')
2929 return
2930 if not wx.TheClipboard.Open():
2931 _log.debug('cannot open clipboard')
2932 return
2933 data_obj = wx.TextDataObject()
2934
2935 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2936 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2937 txt = self._rclicked_row_cells[col_idx]
2938
2939 data_obj.SetText(txt)
2940 wx.TheClipboard.SetData(data_obj)
2941 wx.TheClipboard.Close()
2942
2943 #------------------------------------------------------------
2945 if wx.TheClipboard.IsOpened():
2946 _log.debug('clipboard already open')
2947 return
2948 if not wx.TheClipboard.Open():
2949 _log.debug('cannot open clipboard')
2950 return
2951 data_obj = wx.TextDataObject()
2952
2953 txt = ''
2954 # get previous text
2955 got_it = wx.TheClipboard.GetData(data_obj)
2956 if got_it:
2957 txt = data_obj.Text + '\n'
2958
2959 # add text
2960 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2961 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2962 txt += self._rclicked_row_cells[col_idx]
2963
2964 # set text
2965 data_obj.SetText(txt)
2966 wx.TheClipboard.SetData(data_obj)
2967 wx.TheClipboard.Close()
2968
2969 #------------------------------------------------------------
2971 if wx.TheClipboard.IsOpened():
2972 _log.debug('clipboard already open')
2973 return
2974 if not wx.TheClipboard.Open():
2975 _log.debug('cannot open clipboard')
2976 return
2977 data_obj = wx.TextDataObject()
2978
2979 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2980 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2981 txt = self._rclicked_row_cells_w_hdr[col_idx]
2982
2983 data_obj.SetText(txt)
2984 wx.TheClipboard.SetData(data_obj)
2985 wx.TheClipboard.Close()
2986
2987 #------------------------------------------------------------
2989 if wx.TheClipboard.IsOpened():
2990 _log.debug('clipboard already open')
2991 return
2992 if not wx.TheClipboard.Open():
2993 _log.debug('cannot open clipboard')
2994 return
2995 data_obj = wx.TextDataObject()
2996
2997 txt = ''
2998 # get previous text
2999 got_it = wx.TheClipboard.GetData(data_obj)
3000 if got_it:
3001 txt = data_obj.Text + '\n'
3002
3003 # add text
3004 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
3005 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
3006 txt += self._rclicked_row_cells_w_hdr[col_idx]
3007
3008 # set text
3009 data_obj.SetText(txt)
3010 wx.TheClipboard.SetData(data_obj)
3011 wx.TheClipboard.Close()
3012
3013 #------------------------------------------------------------
3014 # search related methods
3015 #------------------------------------------------------------
3016 # def _on_lost_focus(self, evt):
3017 # evt.Skip()
3018 # if self.__search_dlg is None:
3019 # return
3020 ## print self.FindFocus()
3021 ## print self.__search_dlg
3022 # #self.__search_dlg.Close()
3023
3024 #------------------------------------------------------------
3026 #print "search_match"
3027 if self.__search_term is None:
3028 #print "no search term"
3029 return False
3030 if self.__search_term.strip() == '':
3031 #print "empty search term"
3032 return False
3033 if self.__searchable_cols is None:
3034 #print "searchable cols not initialized, now setting"
3035 self.searchable_columns = None
3036 if len(self.__searchable_cols) == 0:
3037 #print "no cols to search"
3038 return False
3039
3040 #print "on searching for match on:", self.__search_term
3041 for row_idx in range(self.__next_line_to_search, self.ItemCount):
3042 for col_idx in range(self.ColumnCount):
3043 if col_idx not in self.__searchable_cols:
3044 continue
3045 col_val = self.GetItem(row_idx, col_idx).GetText()
3046 if regex.search(self.__search_term, col_val, regex.U | regex.I) is not None:
3047 self.Select(row_idx)
3048 self.EnsureVisible(row_idx)
3049 if row_idx == self.ItemCount - 1:
3050 # wrap around
3051 self.__next_line_to_search = 0
3052 else:
3053 self.__next_line_to_search = row_idx + 1
3054 return True
3055 # wrap around
3056 self.__next_line_to_search = 0
3057 return False
3058
3059 #------------------------------------------------------------
3061 #print "setting searchable cols to:", cols
3062 # zero-based list of which columns to search
3063 if cols is None:
3064 #print "setting searchable cols to:", range(self.ColumnCount)
3065 self.__searchable_cols = range(self.ColumnCount)
3066 return
3067 # weed out columns to be searched which
3068 # don't exist and uniquify them
3069 new_cols = {}
3070 for col in cols:
3071 if col < self.ColumnCount:
3072 new_cols[col] = True
3073 self.__searchable_cols = list(new_cols)
3074
3075 searchable_columns = property(lambda x:x, _set_searchable_cols)
3076
3077 #------------------------------------------------------------
3078 # properties
3079 #------------------------------------------------------------
3082
3084 if callback is None:
3085 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
3086 self.__activate_callback = None
3087 return
3088 if not callable(callback):
3089 raise ValueError('<activate> callback is not a callable: %s' % callback)
3090 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
3091 self.__activate_callback = callback
3092
3093 activate_callback = property(_get_activate_callback, _set_activate_callback)
3094
3095 #------------------------------------------------------------
3098
3100 if callback is None:
3101 self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
3102 self.__select_callback = None
3103 return
3104 if not callable(callback):
3105 raise ValueError('<selected> callback is not a callable: %s' % callback)
3106 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
3107 self.__select_callback = callback
3108
3109 select_callback = property(_get_select_callback, _set_select_callback)
3110
3111 #------------------------------------------------------------
3114
3116 if callback is None:
3117 self.Unbind(wx.EVT_LIST_ITEM_DESELECTED)
3118 self.__deselect_callback = None
3119 return
3120 if not callable(callback):
3121 raise ValueError('<deselected> callback is not a callable: %s' % callback)
3122 self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._on_list_item_deselected)
3123 self.__deselect_callback = callback
3124
3125 deselect_callback = property(_get_deselect_callback, _set_deselect_callback)
3126
3127 #------------------------------------------------------------
3130
3132 if callback is None:
3133 #self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
3134 self.__delete_callback = None
3135 return
3136 if not callable(callback):
3137 raise ValueError('<delete> callback is not a callable: %s' % callback)
3138 #self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
3139 self.__delete_callback = callback
3140
3141 delete_callback = property(_get_delete_callback, _set_delete_callback)
3142
3143 #------------------------------------------------------------
3146
3148 if callback is None:
3149 self.__new_callback = None
3150 return
3151 if not callable(callback):
3152 raise ValueError('<new> callback is not a callable: %s' % callback)
3153 self.__new_callback = callback
3154
3155 new_callback = property(_get_new_callback, _set_new_callback)
3156
3157 #------------------------------------------------------------
3160
3162 if callback is None:
3163 self.__edit_callback = None
3164 return
3165 if not callable(callback):
3166 raise ValueError('<edit> callback is not a callable: %s' % callback)
3167 self.__edit_callback = callback
3168
3169 edit_callback = property(_get_edit_callback, _set_edit_callback)
3170
3171 #------------------------------------------------------------
3173 if callback is not None:
3174 if not callable(callback):
3175 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
3176 self.__item_tooltip_callback = callback
3177
3178 # the callback must be a function which takes a single argument
3179 # the argument is the data for the item the tooltip is on
3180 # the callback must return None if no item tooltip is to be shown
3181 # otherwise it must return a string (possibly with \n)
3182 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
3183
3184 #------------------------------------------------------------
3190
3191 extend_popup_menu_callback = property(lambda x:x, _set_extend_popup_menu_callback)
3192
3193 #------------------------------------------------------------
3194 # ColumnSorterMixin API
3195 #------------------------------------------------------------
3200
3201 #------------------------------------------------------------
3203 col_idx, is_ascending = self.GetSortState()
3204 if col_idx == -1:
3205 _log.debug('outside any column (idx: -1) clicked, ignoring')
3206 return
3207 self._remove_sorting_indicators_from_column_labels()
3208 col_state = self.GetColumn(col_idx)
3209 col_state.Text += self.sort_order_tags[is_ascending]
3210 self.SetColumn(col_idx, col_state)
3211
3212 #------------------------------------------------------------
3214 return (primary_item1_idx, primary_item2_idx)
3215
3216 if self.__secondary_sort_col is None:
3217 return (primary_item1_idx, primary_item2_idx)
3218 if self.__secondary_sort_col == primary_sort_col:
3219 return (primary_item1_idx, primary_item2_idx)
3220
3221 secondary_val1 = self.itemDataMap[primary_item1_idx][self.__secondary_sort_col]
3222 secondary_val2 = self.itemDataMap[primary_item2_idx][self.__secondary_sort_col]
3223
3224 if type(secondary_val1) == type('') and type(secondary_val2) == type(''):
3225 secondary_cmp_result = locale.strcoll(secondary_val1, secondary_val2)
3226 elif type(secondary_val1) == type('') or type(secondary_val2) == type(''):
3227 secondary_cmp_result = locale.strcoll(str(secondary_val1), str(secondary_val2))
3228 else:
3229 secondary_cmp_result = cmp(secondary_val1, secondary_val2)
3230
3231 if secondary_cmp_result == 0:
3232 return (primary_item1_idx, primary_item2_idx)
3233
3234 # make the secondary column always sort ascending
3235 currently_ascending = self._colSortFlag[primary_sort_col]
3236 if currently_ascending:
3237 secondary_val1, secondary_val2 = min(secondary_val1, secondary_val2), max(secondary_val1, secondary_val2)
3238 else:
3239 secondary_val1, secondary_val2 = max(secondary_val1, secondary_val2), min(secondary_val1, secondary_val2)
3240 return (secondary_val1, secondary_val2)
3241
3242 #------------------------------------------------------------
3244 # http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/
3245 # http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python
3246 # PyICU
3247 sort_col, is_ascending = self.GetSortState()
3248 data1 = self.itemDataMap[item1][sort_col]
3249 data2 = self.itemDataMap[item2][sort_col]
3250 if type(data1) == type('') and type(data2) == type(''):
3251 cmp_result = locale.strcoll(data1, data2)
3252 elif type(data1) == type('') or type(data2) == type(''):
3253 cmp_result = locale.strcoll(str(data1), str(data2))
3254 else:
3255 cmp_result = cmp(data1, data2)
3256
3257 #direction = u'ASC'
3258 if not is_ascending:
3259 cmp_result = -1 * cmp_result
3260 #direction = u'DESC'
3261 # debug:
3262 # if cmp_result < 0:
3263 # op1 = u'\u2191 ' # up
3264 # op2 = u'\u2193' # down
3265 # elif cmp_result > 0:
3266 # op2 = u'\u2191 ' # up
3267 # op1 = u'\u2193' # down
3268 # else:
3269 # op1 = u' = '
3270 # op2 = u''
3271 # print u'%s: [%s]%s[%s]%s (%s)' % (direction, data1, op1, data2, op2, cmp_result)
3272
3273 return cmp_result
3274
3275 # defining our own column sorter does not seem to make
3276 # a difference over the default one until we resort to
3277 # something other than locale.strcoll/strxform/cmp for
3278 # actual sorting
3279 #def GetColumnSorter(self):
3280 # return self._unicode_aware_column_sorter
3281
3282 #------------------------------------------------------------
3284 dict2sort = {}
3285 item_count = self.GetItemCount()
3286 if item_count == 0:
3287 return dict2sort
3288 col_count = self.GetColumnCount()
3289 for item_idx in range(item_count):
3290 dict2sort[item_idx] = ()
3291 if col_count == 0:
3292 continue
3293 for col_idx in range(col_count):
3294 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
3295 # debugging:
3296 #print u'[%s:%s] ' % (item_idx, col_idx), self.GetItem(item_idx, col_idx).GetText()
3297
3298 return dict2sort
3299
3300 #------------------------------------------------------------
3302 for tag in self.sort_order_tags.values():
3303 if text.endswith(tag):
3304 text = text[:-len(tag)]
3305 return text
3306
3307 #------------------------------------------------------------
3309 for col_idx in range(self.ColumnCount):
3310 self._remove_sorting_indicator_from_column_label(col_idx)
3311
3312 #------------------------------------------------------------
3314 assert (col_idx > -1), '<col_idx> must be non-negative integer'
3315 if col_idx > self.ColumnCount:
3316 _log.warning('<col_idx>=%s, but .ColumnCount=%s', col_idx, self.ColumnCount)
3317 return
3318
3319 col_state = self.GetColumn(col_idx)
3320 cleaned_header = self.__remove_sorting_indicator(col_state.Text)
3321 if col_state.Text == cleaned_header:
3322 return
3323
3324 col_state.Text = cleaned_header
3325 self.SetColumn(col_idx, col_state)
3326
3327 #------------------------------------------------------------
3329 self.itemDataMap = None
3330 self.SetColumnCount(self.GetColumnCount())
3331 self._remove_sorting_indicators_from_column_labels()
3332
3333 #------------------------------------------------------------
3337
3338 #------------------------------------------------------------
3340 # this MUST NOT call event.Skip() or else the column sorter mixin#
3341 # will not kick in anymore under wxP 3
3342 #event.Skip()
3343 pass
3344 # debugging:
3345 # sort_col, order = self.GetSortState()
3346 # print u'col clicked: %s / sort col: %s / sort direction: %s / sort flags: %s' % (event.GetColumn(), sort_col, order, self._colSortFlag)
3347 # if self.itemDataMap is not None:
3348 # print u'sort items data map:'
3349 # for key, item in self.itemDataMap.items():
3350 # print key, u' -- ', item
3351
3352 #------------------------------------------------------------
3355
3357 if col is None:
3358 self.__secondary_sort_col = None
3359 return
3360 if col > self.GetColumnCount():
3361 raise ValueError('cannot secondary-sort on col [%s], there are only [%s] columns', col, self.GetColumnCount())
3362 self.__secondary_sort_col = col
3363
3364 secondary_sort_column = property(__get_secondary_sort_col, __set_secondary_sort_col)
3365
3366 #------------------------------------------------------------
3368 title = undecorate_window_title(gmTools.coalesce(self.container_title, '').rstrip())
3369 if title != '':
3370 return title
3371
3372 if self.ColumnCount == 0:
3373 return _('list')
3374
3375 col_labels = []
3376 for col_idx in range(self.ColumnCount):
3377 col_label = self.GetColumn(col_idx).Text.strip()
3378 if col_label != '':
3379 col_labels.append(col_label)
3380 return _('list') + '-[%s]' % ']_['.join(col_labels)
3381
3382 useful_title = property(__get_useful_title)
3383
3384 #------------------------------------------------------------
3386 if widget is None:
3387 widget = self
3388 if hasattr(widget, 'GetTitle'):
3389 title = widget.GetTitle().strip()
3390 if title != '':
3391 return title
3392
3393 parent = widget.GetParent()
3394 if parent is None:
3395 return None
3396
3397 return self.__get_container_title(widget = parent)
3398
3399 container_title = property(__get_container_title)
3400
3401 #------------------------------------------------------------
3403 if widget is None:
3404 widget = self
3405 if isinstance(widget, wx.Dialog):
3406 return widget
3407
3408 parent = widget.GetParent()
3409 if parent is None:
3410 return None
3411
3412 return self.__get_containing_dlg(widget = parent)
3413
3414 containing_dlg = property(__get_containing_dlg)
3415
3416 #================================================================
3421
3422 #================================================================
3423 # main
3424 #----------------------------------------------------------------
3425 if __name__ == '__main__':
3426
3427 if len(sys.argv) < 2:
3428 sys.exit()
3429
3430 if sys.argv[1] != 'test':
3431 sys.exit()
3432
3433 sys.path.insert(0, '../../')
3434
3435 from Gnumed.pycommon import gmI18N
3436 gmI18N.activate_locale()
3437 gmI18N.install_domain()
3438
3439 #------------------------------------------------------------
3441 app = wx.PyWidgetTester(size = (400, 500))
3442 dlg = wx.MultiChoiceDialog (
3443 parent = None,
3444 message = 'test message',
3445 caption = 'test caption',
3446 choices = ['a', 'b', 'c', 'd', 'e']
3447 )
3448 dlg.ShowModal()
3449 sels = dlg.GetSelections()
3450 print("selected:")
3451 for sel in sels:
3452 print(sel)
3453
3454 #------------------------------------------------------------
3460
3461 def refresh(lctrl):
3462 choices = ['a', 'b', 'c']
3463 lctrl.set_string_items(choices)
3464
3465 app = wx.App()
3466 chosen = get_choices_from_list (
3467 # msg = 'select a health issue\nfrom the list below\n',
3468 caption = 'select health issues',
3469 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
3470 #columns = ['issue', 'no of episodes']
3471 columns = ['issue'],
3472 refresh_callback = refresh,
3473 single_selection = False
3474 #, edit_callback = edit
3475 )
3476 print("chosen:")
3477 print(chosen)
3478
3479 #------------------------------------------------------------
3481 #app = wx.PyWidgetTester(size = (200, 50))
3482 app = wx.App(size = (200, 50))
3483 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
3484 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
3485 #dlg.set_columns(['Plugins'], [])
3486 dlg.set_string_items(['patient', 'emr', 'docs'])
3487 result = dlg.ShowModal()
3488 print(result)
3489 print(dlg.get_picks())
3490
3491 #------------------------------------------------------------
3492 test_get_choices_from_list()
3493 #test_wxMultiChoiceDialog()
3494 #test_item_picker_dlg()
3495
3496 #================================================================
3497
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Jul 23 01:55:31 2020 | http://epydoc.sourceforge.net |