| 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 = _('generic multi choice dialog')
94
95 if single_selection:
96 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
97 else:
98 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
99
100 dlg.refresh_callback = refresh_callback
101 dlg.edit_callback = edit_callback
102 dlg.new_callback = new_callback
103 dlg.delete_callback = delete_callback
104 dlg.list_tooltip_callback = list_tooltip_callback
105
106 dlg.can_return_empty = can_return_empty
107 dlg.ignore_OK_button = ignore_OK_button
108 dlg.left_extra_button = left_extra_button
109 dlg.middle_extra_button = middle_extra_button
110 dlg.right_extra_button = right_extra_button
111
112 dlg.set_columns(columns = columns)
113
114 if refresh_callback is None:
115 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible
116 dlg.set_column_widths()
117
118 if data is not None:
119 dlg.set_data(data = data) # can override data set if refresh_callback is not None
120
121 if selections is not None:
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.Destroy()
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: msg = None
147
148 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
149
150 self.message = msg
151
152 self.left_extra_button = None
153 self.middle_extra_button = None
154 self.right_extra_button = None
155
156 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
157 self.new_callback = None # called when NEW button pressed, no argument passed in
158 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
159 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
160
161 self.can_return_empty = False
162 self.ignore_OK_button = False # by default do show/use the OK button
163
164 self.select_callback = None # called when an item is selected, data of topmost selected item passed in
165 self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
166
167 #------------------------------------------------------------
169 self._LCTRL_items.set_columns(columns = columns)
170
171 #------------------------------------------------------------
173 self._LCTRL_items.set_column_widths(widths = widths)
174
175 #------------------------------------------------------------
177 self._LCTRL_items.set_string_items(items = items, reshow = reshow)
178 self._LCTRL_items.set_column_widths()
179 #self._LCTRL_items.Select(0)
180
181 #------------------------------------------------------------
183 self._LCTRL_items.set_selections(selections = selections)
184 if selections is None:
185 return
186 if len(selections) == 0:
187 return
188 if self.ignore_OK_button:
189 return
190 self._BTN_ok.Enable(True)
191 self._BTN_ok.SetDefault()
192
193 #------------------------------------------------------------
196
197 #------------------------------------------------------------
199 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
200
201 #------------------------------------------------------------
202 # event handlers
203 #------------------------------------------------------------
205 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
206 if not self.can_return_empty:
207 self._BTN_cancel.SetDefault()
208 self._BTN_ok.Enable(False)
209 self._BTN_edit.Enable(False)
210 self._BTN_delete.Enable(False)
211
212 event.Skip()
213
214 #------------------------------------------------------------
218
219 #------------------------------------------------------------
225
226 #------------------------------------------------------------
255
256 #------------------------------------------------------------
275
276 #------------------------------------------------------------
295
296 #------------------------------------------------------------
315
316 #------------------------------------------------------------
317 # internal helpers
318 #------------------------------------------------------------
320 event.Skip()
321 if not self.__ignore_OK_button:
322 self._BTN_ok.SetDefault()
323 self._BTN_ok.Enable(True)
324 if self.edit_callback is not None:
325 self._BTN_edit.Enable(True)
326 if self.delete_callback is not None:
327 self._BTN_delete.Enable(True)
328 if self.__select_callback is not None:
329 item = self._LCTRL_items.get_selected_item_data(only_one = True)
330 self.__select_callback(item)
331
332 #------------------------------------------------------------
335
336 #------------------------------------------------------------
338 any_deleted = False
339 for item_data in self._LCTRL_items.get_selected_item_data(only_one = False):
340 if item_data is None:
341 continue
342 if self.__delete_callback(item_data):
343 any_deleted = True
344
345 self._LCTRL_items.SetFocus()
346
347 if any_deleted is False:
348 return
349
350 if self.__refresh_callback is None:
351 return
352
353 wx.BeginBusyCursor()
354 try:
355 self.__refresh_callback(lctrl = self._LCTRL_items)
356 self._LCTRL_items.set_column_widths()
357 finally:
358 wx.EndBusyCursor()
359
360 #------------------------------------------------------------
363
364 #------------------------------------------------------------
366 if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
367 self._LCTRL_items.SetFocus()
368 return
369 if self.__refresh_callback is None:
370 self._LCTRL_items.SetFocus()
371 return
372 wx.BeginBusyCursor()
373 try:
374 self.__refresh_callback(lctrl = self._LCTRL_items)
375 self._LCTRL_items.set_column_widths()
376 self._LCTRL_items.SetFocus()
377 finally:
378 wx.EndBusyCursor()
379
380 #------------------------------------------------------------
383
384 #------------------------------------------------------------
386 if not self.__new_callback():
387 self._LCTRL_items.SetFocus()
388 return
389 if self.__refresh_callback is None:
390 self._LCTRL_items.SetFocus()
391 return
392 wx.BeginBusyCursor()
393 try:
394 self.__refresh_callback(lctrl = self._LCTRL_items)
395 self._LCTRL_items.set_column_widths()
396 self._LCTRL_items.SetFocus()
397 finally:
398 wx.EndBusyCursor()
399
400 #------------------------------------------------------------
401 # properties
402 #------------------------------------------------------------
416
417 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
418
419 #------------------------------------------------------------
442
443 left_extra_button = property(lambda x:x, _set_left_extra_button)
444
445 #------------------------------------------------------------
468
469 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
470
471 #------------------------------------------------------------
494
495 right_extra_button = property(lambda x:x, _set_right_extra_button)
496
497 #------------------------------------------------------------
500
502 if callback is not None:
503 if self.__refresh_callback is None:
504 raise ValueError('refresh callback must be set before new callback can be set')
505 if not callable(callback):
506 raise ValueError('<new> callback is not a callable: %s' % callback)
507 self.__new_callback = callback
508
509 if callback is None:
510 self._BTN_new.Enable(False)
511 self._BTN_new.Hide()
512 self._LCTRL_items.new_callback = None
513 else:
514 self._BTN_new.Enable(True)
515 self._BTN_new.Show()
516 self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
517
518 new_callback = property(_get_new_callback, _set_new_callback)
519
520 #------------------------------------------------------------
523
525 if callback is not None:
526 if not callable(callback):
527 raise ValueError('<edit> callback is not a callable: %s' % callback)
528 self.__edit_callback = callback
529
530 if callback is None:
531 self._BTN_edit.Enable(False)
532 self._BTN_edit.Hide()
533 self._LCTRL_items.edit_callback = None
534 else:
535 self._BTN_edit.Enable(True)
536 self._BTN_edit.Show()
537 self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
538
539 edit_callback = property(_get_edit_callback, _set_edit_callback)
540
541 #------------------------------------------------------------
544
546 if callback is not None:
547 if self.__refresh_callback is None:
548 raise ValueError('refresh callback must be set before delete callback can be set')
549 if not callable(callback):
550 raise ValueError('<delete> callback is not a callable: %s' % callback)
551 self.__delete_callback = callback
552 if callback is None:
553 self._BTN_delete.Enable(False)
554 self._BTN_delete.Hide()
555 self._LCTRL_items.delete_callback = None
556 else:
557 self._BTN_delete.Enable(True)
558 self._BTN_delete.Show()
559 self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
560
561 delete_callback = property(_get_delete_callback, _set_delete_callback)
562
563 #------------------------------------------------------------
566
568 wx.BeginBusyCursor()
569 try:
570 self.__refresh_callback(lctrl = self._LCTRL_items)
571 finally:
572 wx.EndBusyCursor()
573 self._LCTRL_items.set_column_widths()
574
576 if callback is not None:
577 if not callable(callback):
578 raise ValueError('<refresh> callback is not a callable: %s' % callback)
579 self.__refresh_callback = callback
580 if callback is not None:
581 wx.CallAfter(self._set_refresh_callback_helper)
582
583 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
584
585 #------------------------------------------------------------
588
590 if callback is not None:
591 if not callable(callback):
592 raise ValueError('<select> callback is not a callable: %s' % callback)
593 self.__select_callback = callback
594
595 select_callback = property(_get_select_callback, _set_select_callback)
596
597 #------------------------------------------------------------
599 self._LCTRL_items.item_tooltip_callback = callback
600
601 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
602 #def _get_tooltip(self, item): # inside a class
603 #def _get_tooltip(item): # outside a class
604 #------------------------------------------------------------
606 if message is None:
607 self._LBL_message.Hide()
608 return
609 self._LBL_message.SetLabel(message)
610 self._LBL_message.Show()
611
612 message = property(lambda x:x, _set_message)
613
614 #================================================================
615 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
616
618 """A panel holding a generic multi-column list and action buttions."""
619
621
622 try:
623 msg = kwargs['msg']
624 del kwargs['msg']
625 except KeyError: msg = None
626
627 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
628
629 if msg is None:
630 self._LBL_message.Hide()
631 else:
632 self._LBL_message.SetLabel(msg)
633
634 self.left_extra_button = None
635 self.middle_extra_button = None
636 self.right_extra_button = None
637
638 # new/edit/delete must return True/False to enable refresh
639 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
640 self.new_callback = None # called when NEW button pressed, no argument passed in
641 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
642 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
643
644 self.select_callback = None # called when an item is selected, data of topmost selected item passed in
645 self._LCTRL_items.select_callback = self._on_list_item_selected_in_listctrl
646
647 #------------------------------------------------------------
648 # external API
649 #------------------------------------------------------------
651 self._LCTRL_items.set_columns(columns = columns)
652
653 #------------------------------------------------------------
655 self._LCTRL_items.set_string_items(items = items, reshow = reshow)
656 self._LCTRL_items.set_column_widths()
657
658 if (items is None) or (len(items) == 0):
659 self._BTN_edit.Enable(False)
660 self._BTN_remove.Enable(False)
661 #else:
662 # self._LCTRL_items.Select(0)
663
664 #------------------------------------------------------------
667
668 #------------------------------------------------------------
671
672 #------------------------------------------------------------
674 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
675
676 #------------------------------------------------------------
677 # internal helpers
678 #------------------------------------------------------------
680 event.Skip()
681 if self.__edit_callback is not None:
682 self._BTN_edit.Enable(True)
683 if self.__delete_callback is not None:
684 self._BTN_remove.Enable(True)
685 if self.__select_callback is not None:
686 item = self._LCTRL_items.get_selected_item_data(only_one = True)
687 self.__select_callback(item)
688
689 #------------------------------------------------------------
692
693 #------------------------------------------------------------
695 if not self.__delete_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
696 return
697 if self.__refresh_callback is None:
698 self._LCTRL_items.SetFocus()
699 return
700 wx.BeginBusyCursor()
701 try:
702 self.__refresh_callback(lctrl = self._LCTRL_items)
703 self._LCTRL_items.set_column_widths()
704 self._LCTRL_items.SetFocus()
705 finally:
706 wx.EndBusyCursor()
707
708 #------------------------------------------------------------
711
712 #------------------------------------------------------------
714 if not self.__edit_callback(self._LCTRL_items.get_selected_item_data(only_one = True)):
715 self._LCTRL_items.SetFocus()
716 return
717 if self.__refresh_callback is None:
718 self._LCTRL_items.SetFocus()
719 return
720 wx.BeginBusyCursor()
721 try:
722 self.__refresh_callback(lctrl = self._LCTRL_items)
723 self._LCTRL_items.set_column_widths()
724 self._LCTRL_items.SetFocus()
725 finally:
726 wx.EndBusyCursor()
727
728 #------------------------------------------------------------
731
732 #------------------------------------------------------------
734 if not self.__new_callback():
735 self._LCTRL_items.SetFocus()
736 return
737 if self.__refresh_callback is None:
738 self._LCTRL_items.SetFocus()
739 return
740 wx.BeginBusyCursor()
741 try:
742 self.__refresh_callback(lctrl = self._LCTRL_items)
743 self._LCTRL_items.set_column_widths()
744 self._LCTRL_items.SetFocus()
745 finally:
746 wx.EndBusyCursor()
747
748 #------------------------------------------------------------
749 # event handlers
750 #------------------------------------------------------------
752 event.Skip()
753 if self._LCTRL_items.get_selected_items(only_one = True) == -1:
754 self._BTN_edit.Enable(False)
755 self._BTN_remove.Enable(False)
756 if self.__select_callback is not None:
757 self.__select_callback(None)
758
759 #------------------------------------------------------------
761 event.Skip()
762 if self.__edit_callback is None:
763 return
764 self._on_edit_button_pressed(event)
765
766 #------------------------------------------------------------
777
778 #------------------------------------------------------------
792
793 #------------------------------------------------------------
798
799 #------------------------------------------------------------
815
816 #------------------------------------------------------------
832
833 #------------------------------------------------------------
849
850 #------------------------------------------------------------
851 # properties
852 #------------------------------------------------------------
855
857 if callback is not None:
858 if self.__refresh_callback is None:
859 raise ValueError('refresh callback must be set before new callback can be set')
860 if not callable(callback):
861 raise ValueError('<new> callback is not a callable: %s' % callback)
862 self.__new_callback = callback
863
864 if callback is None:
865 self._BTN_add.Enable(False)
866 self._BTN_add.Hide()
867 self._LCTRL_items.new_callback = None
868 else:
869 self._BTN_add.Enable(True)
870 self._BTN_add.Show()
871 self._LCTRL_items.new_callback = self._on_insert_key_pressed_in_listctrl
872
873 new_callback = property(_get_new_callback, _set_new_callback)
874
875 #------------------------------------------------------------
878
880 if callback is not None:
881 if not callable(callback):
882 raise ValueError('<edit> callback is not a callable: %s' % callback)
883 self.__edit_callback = callback
884
885 if callback is None:
886 self._BTN_edit.Enable(False)
887 self._BTN_edit.Hide()
888 self._LCTRL_items.edit_callback = None
889 else:
890 self._BTN_edit.Enable(True)
891 self._BTN_edit.Show()
892 self._LCTRL_items.edit_callback = self._on_edit_invoked_in_listctrl
893
894 edit_callback = property(_get_edit_callback, _set_edit_callback)
895
896 #------------------------------------------------------------
899
901 if callback is not None:
902 if self.__refresh_callback is None:
903 raise ValueError('refresh callback must be set before delete callback can be set')
904 if not callable(callback):
905 raise ValueError('<delete> callback is not a callable: %s' % callback)
906 self.__delete_callback = callback
907 if callback is None:
908 self._BTN_remove.Enable(False)
909 self._BTN_remove.Hide()
910 self._LCTRL_items.delete_callback = None
911 else:
912 self._BTN_remove.Enable(True)
913 self._BTN_remove.Show()
914 self._LCTRL_items.delete_callback = self._on_delete_key_pressed_in_listctrl
915
916 delete_callback = property(_get_delete_callback, _set_delete_callback)
917
918 #------------------------------------------------------------
921
923 wx.BeginBusyCursor()
924 try:
925 self.__refresh_callback(lctrl = self._LCTRL_items)
926 finally:
927 wx.EndBusyCursor()
928 self._LCTRL_items.set_column_widths()
929
931 if callback is not None:
932 if not callable(callback):
933 raise ValueError('<refresh> callback is not a callable: %s' % callback)
934 self.__refresh_callback = callback
935 if callback is not None:
936 wx.CallAfter(self._set_refresh_callback_helper)
937
938 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
939
940 #------------------------------------------------------------
943
945 if callback is not None:
946 if not callable(callback):
947 raise ValueError('<select> callback is not a callable: %s' % callback)
948 self.__select_callback = callback
949
950 select_callback = property(_get_select_callback, _set_select_callback)
951
952 #------------------------------------------------------------
954 return self._LBL_message.GetLabel()
955
957 if msg is None:
958 self._LBL_message.Hide()
959 self._LBL_message.SetLabel('')
960 else:
961 self._LBL_message.SetLabel(msg)
962 self._LBL_message.Show()
963 self.Layout()
964
965 message = property(_get_message, _set_message)
966
967 #------------------------------------------------------------
983
984 left_extra_button = property(lambda x:x, _set_left_extra_button)
985
986 #------------------------------------------------------------
1002
1003 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
1004
1005 #------------------------------------------------------------
1021
1022 right_extra_button = property(lambda x:x, _set_right_extra_button)
1023
1024 #================================================================
1025 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
1026
1028
1030
1031 try:
1032 msg = kwargs['msg']
1033 del kwargs['msg']
1034 except KeyError:
1035 msg = None
1036
1037 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
1038
1039 if msg is None:
1040 self._LBL_msg.Hide()
1041 else:
1042 self._LBL_msg.SetLabel(msg)
1043
1044 self.ignore_dupes_on_picking = True
1045
1046 self._LCTRL_left.activate_callback = self.__pick_selected
1047 self.__extra_button_callback = None
1048
1049 self._LCTRL_left.SetFocus()
1050
1051 #------------------------------------------------------------
1052 # external API
1053 #------------------------------------------------------------
1055 self._LCTRL_left.set_columns(columns = columns)
1056 if columns_right is None:
1057 self._LCTRL_right.set_columns(columns = columns)
1058 else:
1059 if len(columns_right) < len(columns):
1060 cols = columns
1061 else:
1062 cols = columns_right[:len(columns)]
1063 self._LCTRL_right.set_columns(columns = cols)
1064
1065 #------------------------------------------------------------
1067 self._LCTRL_left.set_string_items(items = items, reshow = reshow)
1068 self._LCTRL_left.set_column_widths()
1069 self._LCTRL_right.set_string_items(reshow = False)
1070
1071 self._BTN_left2right.Enable(False)
1072 self._BTN_right2left.Enable(False)
1073
1074 #------------------------------------------------------------
1077
1078 #------------------------------------------------------------
1080 self.set_string_items(items = choices, reshow = reshow)
1081 if data is not None:
1082 self.set_data(data = data)
1083
1084 #------------------------------------------------------------
1086 self._LCTRL_right.set_string_items(picks, reshow = reshow)
1087 self._LCTRL_right.set_column_widths()
1088 if data is not None:
1089 self._LCTRL_right.set_data(data = data)
1090
1091 #------------------------------------------------------------
1094 #------------------------------------------------------------
1096 return self._LCTRL_right.get_item_data()
1097
1098 picks = property(get_picks, lambda x:x)
1099 #------------------------------------------------------------
1115
1116 extra_button = property(lambda x:x, _set_extra_button)
1117 #------------------------------------------------------------
1118 # internal helpers
1119 #------------------------------------------------------------
1121 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
1122 return
1123
1124 right_items = self._LCTRL_right.get_string_items()
1125 right_data = self._LCTRL_right.get_item_data()
1126 if right_data is None:
1127 right_data = []
1128
1129 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
1130 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
1131
1132 if self.ignore_dupes_on_picking is False:
1133 right_items.extend(selected_items)
1134 right_data.extend(selected_data)
1135 self._LCTRL_right.set_string_items(items = right_items, reshow = True)
1136 self._LCTRL_right.set_data(data = right_data)
1137 self._LCTRL_right.set_column_widths()
1138 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
1139 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
1140 return
1141
1142 for sel_item, sel_data in zip(selected_items, selected_data):
1143 if sel_item in right_items:
1144 continue
1145 right_items.append(sel_item)
1146 right_data.append(sel_data)
1147 self._LCTRL_right.set_string_items(items = right_items, reshow = True)
1148 self._LCTRL_right.set_data(data = right_data)
1149 self._LCTRL_right.set_column_widths()
1150 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
1151 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
1152
1153 #------------------------------------------------------------
1155 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
1156 return
1157
1158 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
1159 self._LCTRL_right.remove_item(item_idx)
1160
1161 if self._LCTRL_right.GetItemCount() == 0:
1162 self._BTN_right2left.Enable(False)
1163
1164 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
1165 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
1166
1167 #------------------------------------------------------------
1168 # event handlers
1169 #------------------------------------------------------------
1171 self._BTN_left2right.Enable(True)
1172 #------------------------------------------------------------
1174 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
1175 self._BTN_left2right.Enable(False)
1176 #------------------------------------------------------------
1178 self._BTN_right2left.Enable(True)
1179 #------------------------------------------------------------
1181 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
1182 self._BTN_right2left.Enable(False)
1183 #------------------------------------------------------------
1186 #------------------------------------------------------------
1189 #------------------------------------------------------------
1192 #------------------------------------------------------------
1194 self._LCTRL_left.item_tooltip_callback = callback
1195
1196 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
1197 #------------------------------------------------------------
1199 self._LCTRL_right.item_tooltip_callback = callback
1200
1201 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
1202
1203 #================================================================
1204 -class cReportListCtrl(listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin, wx.ListCtrl):
1205
1206 # sorting: at set_string_items() time all items will be
1207 # adorned with their initial row number as wxPython data,
1208 # this is used later for a) sorting and b) to access
1209 # GNUmed data objects associated with rows,
1210 # the latter are ordered in initial row number order
1211 # at set_data() time
1212
1213 sort_order_tags = {
1214 True: ' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
1215 False: ' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
1216 }
1217
1219
1220 self.debug = None
1221 self.map_item_idx2data_idx = self.GetItemData
1222
1223 try:
1224 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
1225 except KeyError:
1226 kwargs['style'] = wx.LC_REPORT
1227
1228 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
1229
1230 wx.ListCtrl.__init__(self, *args, **kwargs)
1231 listmixins.ListCtrlAutoWidthMixin.__init__(self)
1232
1233 # required for column sorting
1234 self._invalidate_sorting_metadata() # must be called after each (external/direct) list item update
1235 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?)
1236 self.__secondary_sort_col = None
1237 # apparently, this MUST be bound under wxP3 - but why ??
1238 self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
1239
1240 # cols/rows
1241 self.__widths = None
1242 self.__data = None
1243
1244 # event callbacks
1245 self.__select_callback = None
1246 self.__deselect_callback = None
1247 self.__activate_callback = None
1248 self.__new_callback = None
1249 self.__edit_callback = None
1250 self.__delete_callback = None
1251
1252 # context menu
1253 self.__extend_popup_menu_callback = None
1254 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked) # (also handled by MENU key on EVT_LIST_KEY_DOWN)
1255
1256 # row tooltips
1257 self.__item_tooltip_callback = None
1258 self.__tt_last_item = None
1259 # self.__tt_static_part_base = _(
1260 # u'Select the items you want to work on.\n'
1261 # u'\n'
1262 # u'A discontinuous selection may depend on your holding '
1263 # u'down a platform-dependent modifier key (<CTRL>, <ALT>, '
1264 # u'etc) or key combination (eg. <CTRL-SHIFT> or <CTRL-ALT>) '
1265 # u'while clicking.'
1266 # )
1267 self.__tt_static_part_base = ''
1268 self.__tt_static_part = self.__tt_static_part_base
1269 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
1270
1271 # search related:
1272 self.__search_term = None
1273 self.__next_line_to_search = 0
1274 self.__searchable_cols = None
1275
1276 # general event handling
1277 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
1278 self.Bind(wx.EVT_CHAR, self._on_char) # CTRL-F / CTRL-N (LIST_KEY_DOWN does not support modifiers)
1279 self.Bind(wx.EVT_LIST_KEY_DOWN, self._on_list_key_down) # context menu key -> context menu / DEL / INS
1280
1281 #------------------------------------------------------------
1282 # debug sizing
1283 #------------------------------------------------------------
1285 if self.debug is None:
1286 return False
1287 if not self.debug.endswith('_sizing'):
1288 return False
1289 _log.debug('[%s.%s]: *args = (%s), **kwargs = (%s)', self.debug, caller_name, str(args), str(kwargs))
1290 return True
1291
1292 #------------------------------------------------------------
1294 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1295 return super(cReportListCtrl, self).CacheBestSize(*args, **kwargs)
1296
1297 #------------------------------------------------------------
1299 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1300 return super(cReportListCtrl, self).Fit(*args, **kwargs)
1301
1302 #------------------------------------------------------------
1304 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1305 return super(cReportListCtrl, self).FitInside(*args, **kwargs)
1306
1307 #------------------------------------------------------------
1309 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1310 return super(cReportListCtrl, self).InvalidateBestSize(*args, **kwargs)
1311
1312 #------------------------------------------------------------
1314 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1315 return super(cReportListCtrl, self).SetBestFittingSize(*args, **kwargs)
1316
1317 #------------------------------------------------------------
1319 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1320 return super(cReportListCtrl, self).SetInitialSize(*args, **kwargs)
1321
1322 #------------------------------------------------------------
1324 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1325 return super(cReportListCtrl, self).SetClientSize(*args, **kwargs)
1326
1327 #------------------------------------------------------------
1329 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1330 return super(cReportListCtrl, self).SetClientSizeWH(*args, **kwargs)
1331
1332 #------------------------------------------------------------
1334 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1335 return super(cReportListCtrl, self).SetMaxClientSize(*args, **kwargs)
1336
1337 #------------------------------------------------------------
1339 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1340 return super(cReportListCtrl, self).SetMaxSize(*args, **kwargs)
1341
1342 #------------------------------------------------------------
1344 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1345 return super(cReportListCtrl, self).SetMinClientSize(*args, **kwargs)
1346
1347 #------------------------------------------------------------
1349 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1350 return super(cReportListCtrl, self).SetMinSize(*args, **kwargs)
1351
1352 #------------------------------------------------------------
1354 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1355 return super(cReportListCtrl, self).SetSize(*args, **kwargs)
1356
1357 #------------------------------------------------------------
1359 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1360 return super(cReportListCtrl, self).SetSizeHints(*args, **kwargs)
1361
1362 #------------------------------------------------------------
1364 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1365 return super(cReportListCtrl, self).SetSizeHintsSz(*args, **kwargs)
1366
1367 #------------------------------------------------------------
1369 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1370 return super(cReportListCtrl, self).SetSizeWH(*args, **kwargs)
1371
1372 #------------------------------------------------------------
1374 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1375 return super(cReportListCtrl, self).SetVirtualSize(*args, **kwargs)
1376
1377 #------------------------------------------------------------
1379 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1380 return super(cReportListCtrl, self).SetVirtualSizeHints(self, *args, **kwargs)
1381
1382 #------------------------------------------------------------
1384 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1385 return super(cReportListCtrl, self).SetVirtualSizeHintsSz(*args, **kwargs)
1386
1387 #------------------------------------------------------------
1389 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1390 return super(cReportListCtrl, self).SetVirtualSizeWH(*args, **kwargs)
1391
1392 #------------------------------------------------------------
1394 res = super(cReportListCtrl, self).GetAdjustedBestSize(*args, **kwargs)
1395 kwargs['sizing_function_result'] = res
1396 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1397 return res
1398
1399 #------------------------------------------------------------
1401 res = super(cReportListCtrl, self).GetEffectiveMinSize(*args, **kwargs)
1402 kwargs['sizing_function_result'] = res
1403 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1404 return res
1405
1406 #------------------------------------------------------------
1408 res = super(cReportListCtrl, self).GetBestSize(*args, **kwargs)
1409 kwargs['sizing_function_result'] = res
1410 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1411 return res
1412
1413 #------------------------------------------------------------
1415 res = super(cReportListCtrl, self).GetBestSizeTuple(*args, **kwargs)
1416 kwargs['sizing_function_result'] = res
1417 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1418 return res
1419
1420 #------------------------------------------------------------
1422 res = super(cReportListCtrl, self).GetBestVirtualSize(*args, **kwargs)
1423 kwargs['sizing_function_result'] = res
1424 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1425 return res
1426
1427 #------------------------------------------------------------
1429 res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
1430 kwargs['sizing_function_result'] = res
1431 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1432 return res
1433
1434 #------------------------------------------------------------
1436 res = super(cReportListCtrl, self).GetClientSize(*args, **kwargs)
1437 kwargs['sizing_function_result'] = res
1438 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1439 return res
1440
1441 #------------------------------------------------------------
1443 res = super(cReportListCtrl, self).GetMaxClientSize(*args, **kwargs)
1444 kwargs['sizing_function_result'] = res
1445 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1446 return res
1447
1448 #------------------------------------------------------------
1450 res = super(cReportListCtrl, self).GetMaxHeight(*args, **kwargs)
1451 kwargs['sizing_function_result'] = res
1452 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1453 return res
1454
1455 #------------------------------------------------------------
1457 res = super(cReportListCtrl, self).GetMaxSize(*args, **kwargs)
1458 kwargs['sizing_function_result'] = res
1459 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1460 return res
1461
1462 #------------------------------------------------------------
1464 res = super(cReportListCtrl, self).GetMaxWidth(*args, **kwargs)
1465 kwargs['sizing_function_result'] = res
1466 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1467 return res
1468
1469 #------------------------------------------------------------
1471 res = super(cReportListCtrl, self).GetMinClientSize(*args, **kwargs)
1472 kwargs['sizing_function_result'] = res
1473 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1474 return res
1475
1476 #------------------------------------------------------------
1478 res = super(cReportListCtrl, self).GetMinHeight(*args, **kwargs)
1479 kwargs['sizing_function_result'] = res
1480 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1481 return res
1482
1483 #------------------------------------------------------------
1485 res = super(cReportListCtrl, self).GetMinSize(*args, **kwargs)
1486 kwargs['sizing_function_result'] = res
1487 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1488 return res
1489
1490 #------------------------------------------------------------
1492 res = super(cReportListCtrl, self).GetMinWidth(*args, **kwargs)
1493 kwargs['sizing_function_result'] = res
1494 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1495 return res
1496
1497 #------------------------------------------------------------
1499 res = super(cReportListCtrl, self).GetSize(*args, **kwargs)
1500 kwargs['sizing_function_result'] = res
1501 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1502 return res
1503
1504 #------------------------------------------------------------
1506 res = super(cReportListCtrl, self).GetVirtualSize(*args, **kwargs)
1507 kwargs['sizing_function_result'] = res
1508 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1509 return res
1510
1511 #------------------------------------------------------------
1513 res = super(cReportListCtrl, self).GetVirtualSizeTuple(*args, **kwargs)
1514 kwargs['sizing_function_result'] = res
1515 self.__log_sizing(sys._getframe().f_code.co_name, *args, **kwargs)
1516 return res
1517
1518 #------------------------------------------------------------
1519 # setters
1520 #------------------------------------------------------------
1522 """(Re)define the columns.
1523
1524 Note that this will (have to) delete the items.
1525 """
1526 self.ClearAll()
1527 self.__tt_last_item = None
1528 if columns is None:
1529 return
1530 for idx in range(len(columns)):
1531 self.InsertColumn(idx, columns[idx])
1532
1533 listmixins.ColumnSorterMixin.__init__(self, 0)
1534 self._invalidate_sorting_metadata()
1535
1536 #------------------------------------------------------------
1538 """Set the column width policy.
1539
1540 widths = None:
1541 use previous policy if any or default policy
1542 widths != None:
1543 use this policy and remember it for later calls
1544
1545 options:
1546 wx.LIST_AUTOSIZE_USEHEADER
1547 wx.LIST_AUTOSIZE
1548
1549 This means there is no way to *revert* to the default policy :-(
1550 """
1551 # explicit policy ?
1552 if widths is not None:
1553 self.__widths = widths
1554 for idx in range(len(self.__widths)):
1555 self.SetColumnWidth(idx, self.__widths[idx])
1556 return
1557
1558 # previous policy ?
1559 if self.__widths is not None:
1560 for idx in range(len(self.__widths)):
1561 self.SetColumnWidth(idx, self.__widths[idx])
1562 return
1563
1564 # default policy !
1565 if self.GetItemCount() == 0:
1566 width_type = wx.LIST_AUTOSIZE_USEHEADER
1567 else:
1568 width_type = wx.LIST_AUTOSIZE
1569 for idx in range(self.GetColumnCount()):
1570 self.SetColumnWidth(idx, width_type)
1571
1572 #------------------------------------------------------------
1574 if column != 'LAST':
1575 if column > self.ColumnCount:
1576 return
1577 # this column will take up all remaining space courtesy of the width mixin
1578 self.setResizeColumn(column)
1579
1580 #------------------------------------------------------------
1582 tries = 0
1583 while tries < max_tries:
1584 if self.debug is not None:
1585 if self.debug.endswith('_deleting'):
1586 _log.debug('[round %s] <%s>.GetItemCount() before DeleteAllItems(): %s (thread [%s])', tries, self.debug, self.GetItemCount(), threading.get_ident())
1587 if not self.DeleteAllItems():
1588 _log.error('<%s>.DeleteAllItems() failed', self.debug)
1589 item_count = self.GetItemCount()
1590 if item_count == 0:
1591 return True
1592 wx.SafeYield(None, True)
1593 _log.error('<%s>.GetItemCount() not 0 (rather: %s) after DeleteAllItems()', self.debug, item_count)
1594 time.sleep(0.3)
1595 wx.SafeYield(None, True)
1596 tries += 1
1597
1598 _log.error('<%s>: unable to delete list items after looping %s times', self.debug, max_tries)
1599 return False
1600
1601 #------------------------------------------------------------
1603 """All item members must be str()able or None."""
1604
1605 wx.BeginBusyCursor()
1606 self._invalidate_sorting_metadata()
1607
1608 if self.ItemCount == 0:
1609 topmost_visible = 0
1610 else:
1611 topmost_visible = self.GetFirstSelected()
1612 if topmost_visible == -1:
1613 topmost_visible = self.GetFocusedItem()
1614 if topmost_visible == -1:
1615 topmost_visible = self.TopItem
1616
1617 if not self.remove_items_safely(max_tries = 3):
1618 _log.error("cannot remove items (!?), continuing and hoping for the best")
1619
1620 if items is None:
1621 self.data = None
1622 wx.EndBusyCursor()
1623 return
1624
1625 # insert new items
1626 for item in items:
1627 try:
1628 item[0]
1629 if not isinstance(item, str):
1630 is_numerically_iterable = True
1631 # do not iterate over individual chars in a string, however
1632 else:
1633 is_numerically_iterable = False
1634 except TypeError:
1635 is_numerically_iterable = False
1636
1637 if is_numerically_iterable:
1638 # cannot use errors='replace' since then
1639 # None/ints/unicode strings fail to get encoded
1640 col_val = str(item[0])
1641 row_num = self.InsertItem(index = sys.maxsize, label = col_val)
1642 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1643 col_val = str(item[col_num])
1644 self.SetItem(index = row_num, column = col_num, label = col_val)
1645 else:
1646 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded
1647 col_val = str(item)
1648 row_num = self.InsertItem(index = sys.maxsize, label = col_val)
1649
1650 if reshow:
1651 if self.ItemCount > 0:
1652 if topmost_visible < self.ItemCount:
1653 self.EnsureVisible(topmost_visible)
1654 self.Focus(topmost_visible)
1655 else:
1656 self.EnsureVisible(self.ItemCount - 1)
1657 self.Focus(self.ItemCount - 1)
1658
1659 # set data to be a copy of items
1660 self.data = items
1661
1662 wx.EndBusyCursor()
1663
1664 #--------------------------
1666 if self.ItemCount == 0:
1667 return []
1668
1669 rows = []
1670 for row_idx in range(self.ItemCount):
1671 row = []
1672 for col_idx in range(self.ColumnCount):
1673 row.append(self.GetItem(row_idx, col_idx).GetText())
1674 rows.append(row)
1675 return rows
1676
1677 # old: only returned first column
1678 #return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
1679
1680 string_items = property(get_string_items, set_string_items)
1681
1682 #------------------------------------------------------------
1684 if len(new_items) == 0:
1685 return
1686
1687 if new_data is None:
1688 new_data = new_items
1689
1690 existing_data = self.get_item_data()
1691 if existing_data is None:
1692 existing_data = []
1693
1694 if allow_dupes:
1695 self._LCTRL_right.set_string_items (
1696 items = self.string_items.extend(new_items),
1697 reshow = True
1698 )
1699 self.data = existing_data.extend(new_data)
1700 self.set_column_widths()
1701 return
1702
1703 existing_items = self.get_string_items()
1704 for new_item, new_data in zip(new_items, new_data):
1705 if new_item in existing_items:
1706 continue
1707 existing_items.append(new_item)
1708 existing_data.append(new_data)
1709 self.set_string_items (
1710 items = existing_items,
1711 reshow = True
1712 )
1713 self.data = existing_data
1714 self.set_column_widths()
1715
1716 #------------------------------------------------------------
1718 """<data> assumed to be a list corresponding to the item indices"""
1719 if data is not None:
1720 item_count = self.GetItemCount()
1721 if len(data) != item_count:
1722 _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())
1723 for item_idx in range(len(data)):
1724 self.SetItemData(item_idx, item_idx)
1725 self.__data = data
1726 self.__tt_last_item = None
1727 # string data (rows/visible list items) not modified,
1728 # so no need to call _update_sorting_metadata
1729 return
1730
1732 # slower than "return self.__data" but helps with detecting
1733 # problems with len(__data) != self.GetItemCount(),
1734 # also takes care of returning data in the order corresponding
1735 # to the order get_string_items returns rows
1736 return self.get_item_data() # returns all data since item_idx is None
1737
1738 data = property(_get_data, set_data)
1739
1740 #------------------------------------------------------------
1742 # not sure why this is done:
1743 if self.GetItemCount() > 0:
1744 self.Select(0, on = 0)
1745 if selections is None:
1746 return
1747 for idx in selections:
1748 self.Select(idx = idx, on = 1)
1749
1751 if self.ItemCount == 0:
1752 return []
1753 if self.__is_single_selection:
1754 return [self.GetFirstSelected()]
1755 selections = []
1756 idx = self.GetFirstSelected()
1757 while idx != -1:
1758 selections.append(idx)
1759 idx = self.GetNextSelected(idx)
1760 return selections
1761
1762 selections = property(__get_selections, set_selections)
1763
1764 #------------------------------------------------------------
1765 # getters
1766 #------------------------------------------------------------
1768 labels = []
1769 for col_idx in range(self.ColumnCount):
1770 col = self.GetColumn(col = col_idx)
1771 labels.append(col.Text)
1772 return labels
1773
1774 column_labels = property(get_column_labels, lambda x:x)
1775
1776 #------------------------------------------------------------
1778 if self.ItemCount == 0:
1779 _log.warning('no items')
1780 return None
1781 if item_idx is not None:
1782 return self.GetItem(item_idx)
1783 _log.error('get_item(None) called')
1784 return None
1785
1786 #------------------------------------------------------------
1788 if self.ItemCount == 0:
1789 return []
1790 return [ self.GetItem(item_idx) for item_idx in range(self.ItemCount) ]
1791
1792 items = property(get_items, lambda x:x)
1793
1794 #------------------------------------------------------------
1796
1797 if self.ItemCount == 0:
1798 if self.__is_single_selection or only_one:
1799 return None
1800 return []
1801
1802 if self.__is_single_selection or only_one:
1803 return self.GetFirstSelected()
1804
1805 items = []
1806 idx = self.GetFirstSelected()
1807 while idx != -1:
1808 items.append(idx)
1809 idx = self.GetNextSelected(idx)
1810
1811 return items
1812
1813 selected_items = property(get_selected_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.GetItemText(self.GetFirstSelected())
1825
1826 items = []
1827 idx = self.GetFirstSelected()
1828 while idx != -1:
1829 items.append(self.GetItemText(idx))
1830 idx = self.GetNextSelected(idx)
1831
1832 return items
1833
1834 selected_string_items = property(get_selected_string_items, lambda x:x)
1835
1836 #------------------------------------------------------------
1838
1839 if self.__data is None: # this isn't entirely clean
1840 return None
1841
1842 if item_idx is not None:
1843 return self.__data[self.map_item_idx2data_idx(item_idx)]
1844
1845 # if <idx> is None return all data up to item_count,
1846 # in case of len(__data) != self.GetItemCount() this
1847 # gives the chance to figure out what is going on
1848 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1849
1850 item_data = property(get_item_data, lambda x:x)
1851
1852 #------------------------------------------------------------
1854
1855 if self.__is_single_selection or only_one:
1856 if self.__data is None:
1857 return None
1858 idx = self.GetFirstSelected()
1859 if idx == -1:
1860 return None
1861 return self.__data[self.map_item_idx2data_idx(idx)]
1862
1863 data = []
1864 if self.__data is None:
1865 return data
1866 idx = self.GetFirstSelected()
1867 while idx != -1:
1868 data.append(self.__data[self.map_item_idx2data_idx(idx)])
1869 idx = self.GetNextSelected(idx)
1870
1871 return data
1872
1873 selected_item_data = property(get_selected_item_data, lambda x:x)
1874
1875 #------------------------------------------------------------
1877 self.Select(idx = self.GetFirstSelected(), on = 0)
1878
1879 #------------------------------------------------------------
1881 # do NOT remove the corresponding data because even if
1882 # the item pointing to this data instance is gone all
1883 # other items will still point to their corresponding
1884 # *initial* row numbers
1885 #if self.__data is not None:
1886 # del self.__data[self.map_item_idx2data_idx(item_idx)]
1887 self.DeleteItem(item_idx)
1888 self.__tt_last_item = None
1889 self._invalidate_sorting_metadata()
1890
1891 #------------------------------------------------------------
1892 # internal helpers
1893 #------------------------------------------------------------
2115
2116 #------------------------------------------------------------
2118 if self.__delete_callback is None:
2119 return
2120
2121 no_items = len(self.get_selected_items(only_one = False))
2122 if no_items == 0:
2123 return
2124
2125 if no_items > 1:
2126 question = _(
2127 '%s list items are selected.\n'
2128 '\n'
2129 'Really delete all %s items ?'
2130 ) % (no_items, no_items)
2131 title = _('Deleting list items')
2132 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
2133 dlg = wx.MessageDialog(None, question, title, style)
2134 btn_pressed = dlg.ShowModal()
2135 dlg.Destroy()
2136 if btn_pressed == wx.ID_NO:
2137 self.SetFocus()
2138 return
2139 if btn_pressed == wx.ID_CANCEL:
2140 self.SetFocus()
2141 return
2142
2143 self.__delete_callback()
2144 return
2145
2146 #------------------------------------------------------------
2151
2152 #------------------------------------------------------------
2157
2158 #------------------------------------------------------------
2160 #print "showing search dlg"
2161 if self.__search_term is None:
2162 #print "no prev search term"
2163 default = ''
2164 else:
2165 #print "prev search term:", self.__search_term
2166 default = self.__search_term
2167 search_term = wx.GetTextFromUser (
2168 _('Enter the search term:'),
2169 _('List search'),
2170 default_value = default
2171 )
2172 if search_term.strip() == '':
2173 #print "empty search term"
2174 self.__search_term = None
2175 self.__tt_static_part = self.__tt_static_part_base
2176 return
2177
2178 #print "search term:", search_term
2179 self.__search_term = search_term
2180 self.__tt_static_part = _(
2181 'Current search term: [[%s]]\n'
2182 '\n'
2183 '%s'
2184 ) % (
2185 search_term,
2186 self.__tt_static_part_base
2187 )
2188 self.__search_match()
2189
2190 #------------------------------------------------------------
2191 # event handlers
2192 #------------------------------------------------------------
2194 event.Skip()
2195 if self.__activate_callback is not None:
2196 self.__activate_callback(event)
2197 return
2198 # default double-click / ENTER action: edit
2199 self.__handle_edit()
2200
2201 #------------------------------------------------------------
2203 if self.__select_callback is not None:
2204 self.__select_callback(event)
2205 else:
2206 event.Skip()
2207
2208 #------------------------------------------------------------
2210 if self.__deselect_callback is not None:
2211 self.__deselect_callback(event)
2212 else:
2213 event.Skip()
2214
2215 #------------------------------------------------------------
2219
2220 #------------------------------------------------------------
2222 evt.Skip()
2223
2224 if evt.KeyCode == wx.WXK_DELETE:
2225 self.__handle_delete()
2226 return
2227
2228 if evt.KeyCode == wx.WXK_INSERT:
2229 self.__handle_insert()
2230 return
2231
2232 if evt.KeyCode == wx.WXK_MENU:
2233 self.__show_context_menu(evt.Index)
2234 return
2235
2236 #------------------------------------------------------------
2238
2239 if chr(evt.GetRawKeyCode()) == 'f':
2240 if evt.GetModifiers() == wx.MOD_CMD:
2241 #print "search dialog invoked"
2242 self.__show_search_dialog()
2243 return
2244
2245 if chr(evt.GetRawKeyCode()) == 'n':
2246 if evt.GetModifiers() == wx.MOD_CMD:
2247 #print "search-next key invoked"
2248 self.__search_match()
2249 return
2250
2251 evt.Skip()
2252 return
2253
2254 #------------------------------------------------------------
2256 """Update tooltip on mouse motion.
2257
2258 for s in dir(wx):
2259 if s.startswith('LIST_HITTEST'):
2260 print s, getattr(wx, s)
2261
2262 LIST_HITTEST_ABOVE 1
2263 LIST_HITTEST_BELOW 2
2264 LIST_HITTEST_NOWHERE 4
2265 LIST_HITTEST_ONITEM 672
2266 LIST_HITTEST_ONITEMICON 32
2267 LIST_HITTEST_ONITEMLABEL 128
2268 LIST_HITTEST_ONITEMRIGHT 256
2269 LIST_HITTEST_ONITEMSTATEICON 512
2270 LIST_HITTEST_TOLEFT 1024
2271 LIST_HITTEST_TORIGHT 2048
2272 """
2273 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
2274
2275 # pointer on item related area at all ?
2276 if where_flag not in [
2277 wx.LIST_HITTEST_ONITEMLABEL,
2278 wx.LIST_HITTEST_ONITEMICON,
2279 wx.LIST_HITTEST_ONITEMSTATEICON,
2280 wx.LIST_HITTEST_ONITEMRIGHT,
2281 wx.LIST_HITTEST_ONITEM
2282 ]:
2283 self.__tt_last_item = None # not on any item
2284 self.SetToolTip(self.__tt_static_part)
2285 return
2286
2287 # same item as last time around ?
2288 if self.__tt_last_item == item_idx:
2289 return
2290
2291 # remeber the new item we are on
2292 self.__tt_last_item = item_idx
2293
2294 # HitTest() can return -1 if it so pleases, meaning that no item
2295 # was hit or else that maybe there aren't any items (empty list)
2296 if item_idx == wx.NOT_FOUND:
2297 self.SetToolTip(self.__tt_static_part)
2298 return
2299
2300 # do we *have* item data ?
2301 if self.__data is None:
2302 self.SetToolTip(self.__tt_static_part)
2303 return
2304
2305 # under some circumstances the item_idx returned
2306 # by HitTest() may be out of bounds with respect to
2307 # self.__data, this hints at a sync problem between
2308 # setting display items and associated data
2309 if (
2310 (item_idx > (len(self.__data) - 1))
2311 or
2312 (item_idx < -1)
2313 ):
2314 self.SetToolTip(self.__tt_static_part)
2315 print("*************************************************************")
2316 print("GNUmed has detected an inconsistency with list item tooltips.")
2317 print("")
2318 print("This is not a big problem and you can keep working.")
2319 print("")
2320 print("However, please send us the following so we can fix GNUmed:")
2321 print("")
2322 print("item idx: %s" % item_idx)
2323 print('where flag: %s' % where_flag)
2324 print('data list length: %s' % len(self.__data))
2325 print("*************************************************************")
2326 return
2327
2328 dyna_tt = None
2329 if self.__item_tooltip_callback is not None:
2330 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
2331
2332 if dyna_tt is None:
2333 self.SetToolTip(self.__tt_static_part)
2334 return
2335
2336 self.SetToolTip(dyna_tt)
2337
2338 #------------------------------------------------------------
2339 # context menu event hendlers
2340 #------------------------------------------------------------
2344
2345 #------------------------------------------------------------
2349
2350 #------------------------------------------------------------
2354
2355 #------------------------------------------------------------
2359
2360 #------------------------------------------------------------
2364
2365 #------------------------------------------------------------
2367
2368 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2369 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2370
2371 col_labels = self.column_labels
2372 line = '%s' % ' || '.join(col_labels)
2373 txt_file.write('%s\n' % line)
2374 txt_file.write(('=' * len(line)) + '\n')
2375
2376 for item_idx in range(self.ItemCount):
2377 fields = []
2378 for col_idx in range(self.ColumnCount):
2379 fields.append(self.GetItem(item_idx, col_idx).Text)
2380 txt_file.write('%s\n' % ' || '.join(fields))
2381
2382 txt_file.close()
2383 gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % txt_name)
2384
2385 #------------------------------------------------------------
2387
2388 csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-all_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2389 csv_file = io.open(csv_name, mode = 'wb')
2390
2391 csv_writer = csv.writer(csv_file)
2392 csv_writer.writerow([ l.encode('utf-8') for l in self.column_labels ])
2393 for item_idx in range(self.ItemCount):
2394 fields = []
2395 for col_idx in range(self.ColumnCount):
2396 fields.append(self.GetItem(item_idx, col_idx).Text)
2397 csv_writer.writerow([ f.encode('utf-8') for f in fields ])
2398
2399 csv_file.close()
2400 gmDispatcher.send(signal = 'statustext', msg = _('All rows saved to [%s].') % csv_name)
2401
2402 #------------------------------------------------------------
2404
2405 if (self.__data is None) or (self.__item_tooltip_callback is None):
2406 return
2407
2408 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2409 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2410
2411 for data in self.data:
2412 tt = self.__item_tooltip_callback(data)
2413 if tt is None:
2414 continue
2415 txt_file.write('%s\n\n' % tt)
2416
2417 txt_file.close()
2418 gmDispatcher.send(signal = 'statustext', msg = _('All tooltips saved to [%s].') % txt_name)
2419
2420 #------------------------------------------------------------
2422
2423 if self.__data is None:
2424 return
2425
2426 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%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 if hasattr(data, 'format'):
2431 txt = data.format()
2432 if type(txt) is list:
2433 txt = '\n'.join(txt)
2434 else:
2435 txt = '%s' % data
2436 txt_file.write('%s\n\n' % txt)
2437
2438 txt_file.close()
2439 gmDispatcher.send(signal = 'statustext', msg = _('All data saved to [%s].') % txt_name)
2440
2441 #------------------------------------------------------------
2443
2444 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2445 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2446
2447 col_labels = self.column_labels
2448 line = '%s' % ' || '.join(col_labels)
2449 txt_file.write('%s\n' % line)
2450 txt_file.write(('=' * len(line)) + '\n')
2451
2452 items = self.selected_items
2453 if self.__is_single_selection:
2454 items = [items]
2455
2456 for item_idx in items:
2457 fields = []
2458 for col_idx in range(self.ColumnCount):
2459 fields.append(self.GetItem(item_idx, col_idx).Text)
2460 txt_file.write('%s\n' % ' || '.join(fields))
2461
2462 txt_file.close()
2463 gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % txt_name)
2464
2465 #------------------------------------------------------------
2467
2468 csv_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-some_rows-%s.csv' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2469 csv_file = io.open(csv_name, mode = 'wb')
2470
2471 csv_writer = csv.writer(csv_file)
2472 csv_writer.writerow([ l.encode('utf-8') for l in self.column_labels ])
2473
2474 items = self.selected_items
2475 if self.__is_single_selection:
2476 items = [items]
2477
2478 for item_idx in items:
2479 fields = []
2480 for col_idx in range(self.ColumnCount):
2481 fields.append(self.GetItem(item_idx, col_idx).Text)
2482 csv_writer.writerow([ f.encode('utf-8') for f in fields ])
2483
2484 csv_file.close()
2485 gmDispatcher.send(signal = 'statustext', msg = _('Selected rows saved to [%s].') % csv_name)
2486
2487 #------------------------------------------------------------
2489
2490 if (self.__data is None) or (self.__item_tooltip_callback is None):
2491 return
2492
2493 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_tooltips-%s.txt' % pydt.datetime.now().strftime('%m%d-%H%M%S'))
2494 txt_file = io.open(txt_name, mode = 'wt', encoding = 'utf8')
2495
2496 for data in self.selected_item_data:
2497 tt = self.__item_tooltip_callback(data)
2498 if tt is None:
2499 continue
2500 txt_file.write('%s\n\n' % tt)
2501
2502 txt_file.close()
2503 gmDispatcher.send(signal = 'statustext', msg = _('Selected tooltips saved to [%s].') % txt_name)
2504
2505 #------------------------------------------------------------
2507
2508 if self.__data is None:
2509 return
2510
2511 txt_name = os.path.join(gmTools.gmPaths().home_dir, 'gnumed', 'gm-list_data-%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 if hasattr(data, 'format'):
2516 txt = data.format()
2517 if type(txt) is list:
2518 txt = '\n'.join(txt)
2519 else:
2520 txt = '%s' % data
2521 txt_file.write('%s\n\n' % txt)
2522
2523 txt_file.close()
2524 gmDispatcher.send(signal = 'statustext', msg = _('Selected data saved to [%s].') % txt_name)
2525
2526 #------------------------------------------------------------
2528 if wx.TheClipboard.IsOpened():
2529 _log.debug('clipboard already open')
2530 return
2531 if not wx.TheClipboard.Open():
2532 _log.debug('cannot open clipboard')
2533 return
2534 data_obj = wx.TextDataObject()
2535
2536 if (self.__data is None) or (self.__item_tooltip_callback is None):
2537 txt = self.__tt_static_part
2538 else:
2539 txt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)])
2540 if txt is None:
2541 txt = self.__tt_static_part
2542
2543 data_obj.SetText(txt)
2544 wx.TheClipboard.SetData(data_obj)
2545 wx.TheClipboard.Close()
2546
2547 #------------------------------------------------------------
2549 if wx.TheClipboard.IsOpened():
2550 _log.debug('clipboard already open')
2551 return
2552 if not wx.TheClipboard.Open():
2553 _log.debug('cannot open clipboard')
2554 return
2555
2556 if (self.__data is None) or (self.__item_tooltip_callback is None):
2557 return
2558
2559 tts = []
2560 for data in self.selected_item_data:
2561 tt = self.__item_tooltip_callback(data)
2562 if tt is None:
2563 continue
2564 tts.append(tt)
2565
2566 if len(tts) == 0:
2567 return
2568
2569 data_obj = wx.TextDataObject()
2570 data_obj.SetText('\n\n'.join(tts))
2571 wx.TheClipboard.SetData(data_obj)
2572 wx.TheClipboard.Close()
2573
2574 #------------------------------------------------------------
2576 if wx.TheClipboard.IsOpened():
2577 _log.debug('clipboard already open')
2578 return
2579 if not wx.TheClipboard.Open():
2580 _log.debug('cannot open clipboard')
2581 return
2582 data_obj = wx.TextDataObject()
2583
2584 txt = ''
2585 # get previous text
2586 got_it = wx.TheClipboard.GetData(data_obj)
2587 if got_it:
2588 txt = data_obj.Text + '\n'
2589
2590 # add text
2591 if (self.__data is None) or (self.__item_tooltip_callback is None):
2592 txt += self.__tt_static_part
2593 else:
2594 tmp = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(self._rclicked_row_idx)])
2595 if tmp is None:
2596 txt += self.__tt_static_part
2597 else:
2598 txt += tmp
2599
2600 # set text
2601 data_obj.SetText(txt)
2602 wx.TheClipboard.SetData(data_obj)
2603 wx.TheClipboard.Close()
2604
2605 #------------------------------------------------------------
2607 if wx.TheClipboard.IsOpened():
2608 _log.debug('clipboard already open')
2609 return
2610 if not wx.TheClipboard.Open():
2611 _log.debug('cannot open clipboard')
2612 return
2613
2614 if (self.__data is None) or (self.__item_tooltip_callback is None):
2615 return
2616
2617 tts = []
2618 for data in self.selected_item_data:
2619 tt = self.__item_tooltip_callback(data)
2620 if tt is None:
2621 continue
2622 tts.append(tt)
2623
2624 if len(tts) == 0:
2625 return
2626
2627 data_obj = wx.TextDataObject()
2628 txt = ''
2629 # get previous text
2630 got_it = wx.TheClipboard.GetData(data_obj)
2631 if got_it:
2632 txt = data_obj.Text + '\n\n'
2633 txt += '\n\n'.join(tts)
2634
2635 data_obj.SetText(txt)
2636 wx.TheClipboard.SetData(data_obj)
2637 wx.TheClipboard.Close()
2638
2639 #------------------------------------------------------------
2641 if wx.TheClipboard.IsOpened():
2642 _log.debug('clipboard already open')
2643 return
2644 if not wx.TheClipboard.Open():
2645 _log.debug('cannot open clipboard')
2646 return
2647 data_obj = wx.TextDataObject()
2648 data_obj.SetText(' // '.join(self._rclicked_row_cells))
2649 wx.TheClipboard.SetData(data_obj)
2650 wx.TheClipboard.Close()
2651
2652 #------------------------------------------------------------
2654 if wx.TheClipboard.IsOpened():
2655 _log.debug('clipboard already open')
2656 return
2657 if not wx.TheClipboard.Open():
2658 _log.debug('cannot open clipboard')
2659 return
2660
2661 rows = []
2662 for item_idx in self.selected_items:
2663 rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
2664
2665 data_obj = wx.TextDataObject()
2666 data_obj.SetText('\n\n'.join(rows))
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 data_obj = wx.TextDataObject()
2679
2680 txt = ''
2681 # get previous text
2682 got_it = wx.TheClipboard.GetData(data_obj)
2683 if got_it:
2684 txt = data_obj.Text + '\n'
2685
2686 # add text
2687 txt += ' // '.join(self._rclicked_row_cells)
2688
2689 # set text
2690 data_obj.SetText(txt)
2691 wx.TheClipboard.SetData(data_obj)
2692 wx.TheClipboard.Close()
2693
2694 #------------------------------------------------------------
2696 if wx.TheClipboard.IsOpened():
2697 _log.debug('clipboard already open')
2698 return
2699 if not wx.TheClipboard.Open():
2700 _log.debug('cannot open clipboard')
2701 return
2702
2703 rows = []
2704 for item_idx in self.selected_items:
2705 rows.append(' // '.join([ self.GetItem(item_idx, col_idx).Text.strip() for col_idx in range(self.ColumnCount) ]))
2706
2707 data_obj = wx.TextDataObject()
2708
2709 txt = ''
2710 # get previous text
2711 got_it = wx.TheClipboard.GetData(data_obj)
2712 if got_it:
2713 txt = data_obj.Text + '\n'
2714 txt += '\n\n'.join(rows)
2715
2716 data_obj.SetText(txt)
2717 wx.TheClipboard.SetData(data_obj)
2718 wx.TheClipboard.Close()
2719
2720 #------------------------------------------------------------
2722 if wx.TheClipboard.IsOpened():
2723 _log.debug('clipboard already open')
2724 return
2725 if not wx.TheClipboard.Open():
2726 _log.debug('cannot open clipboard')
2727 return
2728 data_obj = wx.TextDataObject()
2729 data_obj.SetText('\n'.join(self._rclicked_row_cells_w_hdr))
2730 wx.TheClipboard.SetData(data_obj)
2731 wx.TheClipboard.Close()
2732
2733 #------------------------------------------------------------
2735 if wx.TheClipboard.IsOpened():
2736 _log.debug('clipboard already open')
2737 return
2738 if not wx.TheClipboard.Open():
2739 _log.debug('cannot open clipboard')
2740 return
2741 data_obj = wx.TextDataObject()
2742
2743 txt = ''
2744 # get previous text
2745 got_it = wx.TheClipboard.GetData(data_obj)
2746 if got_it:
2747 txt = data_obj.Text + '\n'
2748
2749 # add text
2750 txt += '\n'.join(self._rclicked_row_cells_w_hdr)
2751
2752 # set text
2753 data_obj.SetText(txt)
2754 wx.TheClipboard.SetData(data_obj)
2755 wx.TheClipboard.Close()
2756
2757 #------------------------------------------------------------
2759 if wx.TheClipboard.IsOpened():
2760 _log.debug('clipboard already open')
2761 return
2762 if not wx.TheClipboard.Open():
2763 _log.debug('cannot open clipboard')
2764 return
2765 data_obj = wx.TextDataObject()
2766 txt = self._rclicked_row_data.format()
2767 if type(txt) == type([]):
2768 txt = '\n'.join(txt)
2769 data_obj.SetText(txt)
2770 wx.TheClipboard.SetData(data_obj)
2771 wx.TheClipboard.Close()
2772
2773 #------------------------------------------------------------
2775 if wx.TheClipboard.IsOpened():
2776 _log.debug('clipboard already open')
2777 return
2778 if not wx.TheClipboard.Open():
2779 _log.debug('cannot open clipboard')
2780 return
2781
2782 data_as_txt = []
2783 for data in self.selected_item_data:
2784 if hasattr(data, 'format'):
2785 txt = data.format()
2786 if type(txt) is list:
2787 txt = '\n'.join(txt)
2788 else:
2789 txt = '%s' % data
2790 data_as_txt.append(txt)
2791
2792 data_obj = wx.TextDataObject()
2793 data_obj.SetText('\n\n'.join(data_as_txt))
2794 wx.TheClipboard.SetData(data_obj)
2795 wx.TheClipboard.Close()
2796
2797 #------------------------------------------------------------
2799 if wx.TheClipboard.IsOpened():
2800 _log.debug('clipboard already open')
2801 return
2802 if not wx.TheClipboard.Open():
2803 _log.debug('cannot open clipboard')
2804 return
2805 data_obj = wx.TextDataObject()
2806
2807 txt = ''
2808 # get previous text
2809 got_it = wx.TheClipboard.GetData(data_obj)
2810 if got_it:
2811 txt = data_obj.Text + '\n'
2812
2813 # add text
2814 tmp = self._rclicked_row_data.format()
2815 if type(tmp) == type([]):
2816 txt += '\n'.join(tmp)
2817 else:
2818 txt += tmp
2819
2820 # set text
2821 data_obj.SetText(txt)
2822 wx.TheClipboard.SetData(data_obj)
2823 wx.TheClipboard.Close()
2824
2825 #------------------------------------------------------------
2827 if wx.TheClipboard.IsOpened():
2828 _log.debug('clipboard already open')
2829 return
2830 if not wx.TheClipboard.Open():
2831 _log.debug('cannot open clipboard')
2832 return
2833
2834 data_as_txt = []
2835 for data in self.selected_item_data:
2836 if hasattr(data, 'format'):
2837 txt = data.format()
2838 if type(txt) is list:
2839 txt = '\n'.join(txt)
2840 else:
2841 txt = '%s' % data
2842 data_as_txt.append(txt)
2843
2844 data_obj = wx.TextDataObject()
2845 txt = ''
2846 # get previous text
2847 got_it = wx.TheClipboard.GetData(data_obj)
2848 if got_it:
2849 txt = data_obj.Text + '\n'
2850 txt += '\n'.join(data_as_txt)
2851
2852 # set text
2853 data_obj.SetText(txt)
2854 wx.TheClipboard.SetData(data_obj)
2855 wx.TheClipboard.Close()
2856
2857 #------------------------------------------------------------
2859 if wx.TheClipboard.IsOpened():
2860 _log.debug('clipboard already open')
2861 return
2862 if not wx.TheClipboard.Open():
2863 _log.debug('cannot open clipboard')
2864 return
2865 data_obj = wx.TextDataObject()
2866
2867 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2868 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2869 txt = self._rclicked_row_cells[col_idx]
2870
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 txt = ''
2886 # get previous text
2887 got_it = wx.TheClipboard.GetData(data_obj)
2888 if got_it:
2889 txt = data_obj.Text + '\n'
2890
2891 # add text
2892 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2893 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2894 txt += self._rclicked_row_cells[col_idx]
2895
2896 # set text
2897 data_obj.SetText(txt)
2898 wx.TheClipboard.SetData(data_obj)
2899 wx.TheClipboard.Close()
2900
2901 #------------------------------------------------------------
2903 if wx.TheClipboard.IsOpened():
2904 _log.debug('clipboard already open')
2905 return
2906 if not wx.TheClipboard.Open():
2907 _log.debug('cannot open clipboard')
2908 return
2909 data_obj = wx.TextDataObject()
2910
2911 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2912 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2913 txt = self._rclicked_row_cells_w_hdr[col_idx]
2914
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 txt = ''
2930 # get previous text
2931 got_it = wx.TheClipboard.GetData(data_obj)
2932 if got_it:
2933 txt = data_obj.Text + '\n'
2934
2935 # add text
2936 #col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.split(u':', 1)[0].rstrip(u':')) - 1
2937 col_idx = int(self._context_menu.FindItemById(evt.Id).ItemLabel.rsplit('#', 1)[1].rstrip(']'))
2938 txt += self._rclicked_row_cells_w_hdr[col_idx]
2939
2940 # set text
2941 data_obj.SetText(txt)
2942 wx.TheClipboard.SetData(data_obj)
2943 wx.TheClipboard.Close()
2944
2945 #------------------------------------------------------------
2946 # search related methods
2947 #------------------------------------------------------------
2948 # def _on_lost_focus(self, evt):
2949 # evt.Skip()
2950 # if self.__search_dlg is None:
2951 # return
2952 ## print self.FindFocus()
2953 ## print self.__search_dlg
2954 # #self.__search_dlg.Close()
2955
2956 #------------------------------------------------------------
2958 #print "search_match"
2959 if self.__search_term is None:
2960 #print "no search term"
2961 return False
2962 if self.__search_term.strip() == '':
2963 #print "empty search term"
2964 return False
2965 if self.__searchable_cols is None:
2966 #print "searchable cols not initialized, now setting"
2967 self.searchable_columns = None
2968 if len(self.__searchable_cols) == 0:
2969 #print "no cols to search"
2970 return False
2971
2972 #print "on searching for match on:", self.__search_term
2973 for row_idx in range(self.__next_line_to_search, self.ItemCount):
2974 for col_idx in range(self.ColumnCount):
2975 if col_idx not in self.__searchable_cols:
2976 continue
2977 col_val = self.GetItem(row_idx, col_idx).GetText()
2978 if regex.search(self.__search_term, col_val, regex.U | regex.I) is not None:
2979 self.Select(row_idx)
2980 self.EnsureVisible(row_idx)
2981 if row_idx == self.ItemCount - 1:
2982 # wrap around
2983 self.__next_line_to_search = 0
2984 else:
2985 self.__next_line_to_search = row_idx + 1
2986 return True
2987 # wrap around
2988 self.__next_line_to_search = 0
2989 return False
2990
2991 #------------------------------------------------------------
2993 #print "setting searchable cols to:", cols
2994 # zero-based list of which columns to search
2995 if cols is None:
2996 #print "setting searchable cols to:", range(self.ColumnCount)
2997 self.__searchable_cols = range(self.ColumnCount)
2998 return
2999 # weed out columns to be searched which
3000 # don't exist and uniquify them
3001 new_cols = {}
3002 for col in cols:
3003 if col < self.ColumnCount:
3004 new_cols[col] = True
3005 #print "actually setting searchable cols to:", new_cols.keys()
3006 self.__searchable_cols = new_cols.keys()
3007
3008 searchable_columns = property(lambda x:x, _set_searchable_cols)
3009
3010 #------------------------------------------------------------
3011 # properties
3012 #------------------------------------------------------------
3015
3017 if callback is None:
3018 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
3019 self.__activate_callback = None
3020 return
3021 if not callable(callback):
3022 raise ValueError('<activate> callback is not a callable: %s' % callback)
3023 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
3024 self.__activate_callback = callback
3025
3026 activate_callback = property(_get_activate_callback, _set_activate_callback)
3027
3028 #------------------------------------------------------------
3031
3033 if callback is None:
3034 self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
3035 self.__select_callback = None
3036 return
3037 if not callable(callback):
3038 raise ValueError('<selected> callback is not a callable: %s' % callback)
3039 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
3040 self.__select_callback = callback
3041
3042 select_callback = property(_get_select_callback, _set_select_callback)
3043
3044 #------------------------------------------------------------
3047
3049 if callback is None:
3050 self.Unbind(wx.EVT_LIST_ITEM_DESELECTED)
3051 self.__deselect_callback = None
3052 return
3053 if not callable(callback):
3054 raise ValueError('<deselected> callback is not a callable: %s' % callback)
3055 self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._on_list_item_deselected)
3056 self.__deselect_callback = callback
3057
3058 deselect_callback = property(_get_deselect_callback, _set_deselect_callback)
3059
3060 #------------------------------------------------------------
3063
3065 if callback is None:
3066 #self.Unbind(wx.EVT_LIST_ITEM_SELECTED)
3067 self.__delete_callback = None
3068 return
3069 if not callable(callback):
3070 raise ValueError('<delete> callback is not a callable: %s' % callback)
3071 #self.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_list_item_selected)
3072 self.__delete_callback = callback
3073
3074 delete_callback = property(_get_delete_callback, _set_delete_callback)
3075
3076 #------------------------------------------------------------
3079
3081 if callback is None:
3082 self.__new_callback = None
3083 return
3084 if not callable(callback):
3085 raise ValueError('<new> callback is not a callable: %s' % callback)
3086 self.__new_callback = callback
3087
3088 new_callback = property(_get_new_callback, _set_new_callback)
3089
3090 #------------------------------------------------------------
3093
3095 if callback is None:
3096 self.__edit_callback = None
3097 return
3098 if not callable(callback):
3099 raise ValueError('<edit> callback is not a callable: %s' % callback)
3100 self.__edit_callback = callback
3101
3102 edit_callback = property(_get_edit_callback, _set_edit_callback)
3103
3104 #------------------------------------------------------------
3106 if callback is not None:
3107 if not callable(callback):
3108 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
3109 self.__item_tooltip_callback = callback
3110
3111 # the callback must be a function which takes a single argument
3112 # the argument is the data for the item the tooltip is on
3113 # the callback must return None if no item tooltip is to be shown
3114 # otherwise it must return a string (possibly with \n)
3115 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
3116
3117 #------------------------------------------------------------
3123
3124 extend_popup_menu_callback = property(lambda x:x, _set_extend_popup_menu_callback)
3125
3126 #------------------------------------------------------------
3127 # ColumnSorterMixin API
3128 #------------------------------------------------------------
3133
3134 #------------------------------------------------------------
3136 col_idx, is_ascending = self.GetSortState()
3137 if col_idx == -1:
3138 _log.debug('outside any column (idx: -1) clicked, ignoring')
3139 return
3140 self._remove_sorting_indicators_from_column_headers()
3141 col_state = self.GetColumn(col_idx)
3142 col_state.Text += self.sort_order_tags[is_ascending]
3143 self.SetColumn(col_idx, col_state)
3144
3145 #------------------------------------------------------------
3147 return (primary_item1_idx, primary_item2_idx)
3148
3149 if self.__secondary_sort_col is None:
3150 return (primary_item1_idx, primary_item2_idx)
3151 if self.__secondary_sort_col == primary_sort_col:
3152 return (primary_item1_idx, primary_item2_idx)
3153
3154 secondary_val1 = self.itemDataMap[primary_item1_idx][self.__secondary_sort_col]
3155 secondary_val2 = self.itemDataMap[primary_item2_idx][self.__secondary_sort_col]
3156
3157 if type(secondary_val1) == type('') and type(secondary_val2) == type(''):
3158 secondary_cmp_result = locale.strcoll(secondary_val1, secondary_val2)
3159 elif type(secondary_val1) == type('') or type(secondary_val2) == type(''):
3160 secondary_cmp_result = locale.strcoll(str(secondary_val1), str(secondary_val2))
3161 else:
3162 secondary_cmp_result = cmp(secondary_val1, secondary_val2)
3163
3164 if secondary_cmp_result == 0:
3165 return (primary_item1_idx, primary_item2_idx)
3166
3167 # make the secondary column always sort ascending
3168 currently_ascending = self._colSortFlag[primary_sort_col]
3169 if currently_ascending:
3170 secondary_val1, secondary_val2 = min(secondary_val1, secondary_val2), max(secondary_val1, secondary_val2)
3171 else:
3172 secondary_val1, secondary_val2 = max(secondary_val1, secondary_val2), min(secondary_val1, secondary_val2)
3173 return (secondary_val1, secondary_val2)
3174
3175 #------------------------------------------------------------
3177 # http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/
3178 # http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python
3179 # PyICU
3180 sort_col, is_ascending = self.GetSortState()
3181 data1 = self.itemDataMap[item1][sort_col]
3182 data2 = self.itemDataMap[item2][sort_col]
3183 if type(data1) == type('') and type(data2) == type(''):
3184 cmp_result = locale.strcoll(data1, data2)
3185 elif type(data1) == type('') or type(data2) == type(''):
3186 cmp_result = locale.strcoll(str(data1), str(data2))
3187 else:
3188 cmp_result = cmp(data1, data2)
3189
3190 #direction = u'ASC'
3191 if not is_ascending:
3192 cmp_result = -1 * cmp_result
3193 #direction = u'DESC'
3194 # debug:
3195 # if cmp_result < 0:
3196 # op1 = u'\u2191 ' # up
3197 # op2 = u'\u2193' # down
3198 # elif cmp_result > 0:
3199 # op2 = u'\u2191 ' # up
3200 # op1 = u'\u2193' # down
3201 # else:
3202 # op1 = u' = '
3203 # op2 = u''
3204 # print u'%s: [%s]%s[%s]%s (%s)' % (direction, data1, op1, data2, op2, cmp_result)
3205
3206 return cmp_result
3207
3208 # defining our own column sorter does not seem to make
3209 # a difference over the default one until we resort to
3210 # something other than locale.strcoll/strxform/cmp for
3211 # actual sorting
3212 #def GetColumnSorter(self):
3213 # return self._unicode_aware_column_sorter
3214
3215 #------------------------------------------------------------
3217 dict2sort = {}
3218 item_count = self.GetItemCount()
3219 if item_count == 0:
3220 return dict2sort
3221 col_count = self.GetColumnCount()
3222 for item_idx in range(item_count):
3223 dict2sort[item_idx] = ()
3224 if col_count == 0:
3225 continue
3226 for col_idx in range(col_count):
3227 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
3228 # debugging:
3229 #print u'[%s:%s] ' % (item_idx, col_idx), self.GetItem(item_idx, col_idx).GetText()
3230
3231 return dict2sort
3232
3233 #------------------------------------------------------------
3235 for col_idx in range(self.ColumnCount):
3236 col_state = self.GetColumn(col_idx)
3237 initial_header = col_state.Text
3238 if col_state.Text.endswith(self.sort_order_tags[True]):
3239 col_state.Text = col_state.Text[:-len(self.sort_order_tags[True])]
3240 if col_state.Text.endswith(self.sort_order_tags[False]):
3241 col_state.Text = col_state.Text[:-len(self.sort_order_tags[False])]
3242 if col_state.Text == initial_header:
3243 continue
3244 self.SetColumn(col_idx, col_state)
3245
3246 #------------------------------------------------------------
3248 self.itemDataMap = None
3249 self.SetColumnCount(self.GetColumnCount())
3250 self._remove_sorting_indicators_from_column_headers()
3251
3252 #------------------------------------------------------------
3256
3257 #------------------------------------------------------------
3259 # this MUST NOT call event.Skip() or else the column sorter mixin#
3260 # will not kick in anymore under wxP 3
3261 #event.Skip()
3262 pass
3263 # debugging:
3264 # sort_col, order = self.GetSortState()
3265 # print u'col clicked: %s / sort col: %s / sort direction: %s / sort flags: %s' % (event.GetColumn(), sort_col, order, self._colSortFlag)
3266 # if self.itemDataMap is not None:
3267 # print u'sort items data map:'
3268 # for key, item in self.itemDataMap.items():
3269 # print key, u' -- ', item
3270
3271 #------------------------------------------------------------
3274
3276 if col is None:
3277 self.__secondary_sort_col = None
3278 return
3279 if col > self.GetColumnCount():
3280 raise ValueError('cannot secondary-sort on col [%s], there are only [%s] columns', col, self.GetColumnCount())
3281 self.__secondary_sort_col = col
3282
3283 secondary_sort_column = property(__get_secondary_sort_col, __set_secondary_sort_col)
3284
3285 #================================================================
3290
3291
3292 #================================================================
3293 # main
3294 #----------------------------------------------------------------
3295 if __name__ == '__main__':
3296
3297 if len(sys.argv) < 2:
3298 sys.exit()
3299
3300 if sys.argv[1] != 'test':
3301 sys.exit()
3302
3303 sys.path.insert(0, '../../')
3304
3305 from Gnumed.pycommon import gmI18N
3306 gmI18N.activate_locale()
3307 gmI18N.install_domain()
3308
3309 #------------------------------------------------------------
3311 app = wx.PyWidgetTester(size = (400, 500))
3312 dlg = wx.MultiChoiceDialog (
3313 parent = None,
3314 message = 'test message',
3315 caption = 'test caption',
3316 choices = ['a', 'b', 'c', 'd', 'e']
3317 )
3318 dlg.ShowModal()
3319 sels = dlg.GetSelections()
3320 print("selected:")
3321 for sel in sels:
3322 print(sel)
3323 #------------------------------------------------------------
3329
3330 def refresh(lctrl):
3331 choices = ['a', 'b', 'c']
3332 lctrl.set_string_items(choices)
3333
3334 app = wx.PyWidgetTester(size = (200, 50))
3335 chosen = get_choices_from_list (
3336 # msg = 'select a health issue\nfrom the list below\n',
3337 caption = 'select health issues',
3338 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
3339 #columns = ['issue', 'no of episodes']
3340 columns = ['issue'],
3341 refresh_callback = refresh
3342 #, edit_callback = edit
3343 )
3344 print("chosen:")
3345 print(chosen)
3346 #------------------------------------------------------------
3348 app = wx.PyWidgetTester(size = (200, 50))
3349 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
3350 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
3351 #dlg.set_columns(['Plugins'], [])
3352 dlg.set_string_items(['patient', 'emr', 'docs'])
3353 result = dlg.ShowModal()
3354 print(result)
3355 print(dlg.get_picks())
3356 #------------------------------------------------------------
3357 #test_get_choices_from_list()
3358 #test_wxMultiChoiceDialog()
3359 test_item_picker_dlg()
3360
3361 #================================================================
3362 #
3363
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 10 01:55:20 2018 | http://epydoc.sourceforge.net |