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