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