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