1 """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 thread
25 import time
26 import re as regex
27
28
29 import wx
30 import wx.lib.mixins.listctrl as listmixins
31
32
33 _log = logging.getLogger('gm.list_ui')
34
35
36
37 -def get_choices_from_list (
38 parent=None,
39 msg=None,
40 caption=None,
41 columns=None,
42 choices=None,
43 data=None,
44 selections=None,
45 edit_callback=None,
46 new_callback=None,
47 delete_callback=None,
48 refresh_callback=None,
49 single_selection=False,
50 can_return_empty=False,
51 ignore_OK_button=False,
52 left_extra_button=None,
53 middle_extra_button=None,
54 right_extra_button=None,
55 list_tooltip_callback=None):
56 """Let user select item(s) from a list.
57
58 - new_callback: ()
59 - edit_callback: (item data)
60 - delete_callback: (item data)
61 - refresh_callback: (listctrl)
62 - list_tooltip_callback: (item data)
63
64 - left/middle/right_extra_button: (label, tooltip, <callback> [, wants_list_ctrl])
65 wants_list_ctrl is optional
66 <callback> is called with item_data (or listctrl) as the only argument
67
68 returns:
69 on [CANCEL]: None
70 on [OK]:
71 if any items selected:
72 if single_selection:
73 the data of the selected item
74 else:
75 list of data of selected items
76 else:
77 if can_return_empty is True AND [OK] button was pressed:
78 empty list
79 else:
80 None
81 """
82 if caption is None:
83 caption = _('generic multi choice dialog')
84
85 if single_selection:
86 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
87 else:
88 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
89
90 dlg.refresh_callback = refresh_callback
91 dlg.edit_callback = edit_callback
92 dlg.new_callback = new_callback
93 dlg.delete_callback = delete_callback
94 dlg.list_tooltip_callback = list_tooltip_callback
95
96 dlg.ignore_OK_button = ignore_OK_button
97 dlg.left_extra_button = left_extra_button
98 dlg.middle_extra_button = middle_extra_button
99 dlg.right_extra_button = right_extra_button
100
101 dlg.set_columns(columns = columns)
102
103 if refresh_callback is None:
104 dlg.set_string_items(items = choices)
105 dlg.set_column_widths()
106
107 if data is not None:
108 dlg.set_data(data = data)
109
110 if selections is not None:
111 dlg.set_selections(selections = selections)
112 dlg.can_return_empty = can_return_empty
113
114 btn_pressed = dlg.ShowModal()
115 sels = dlg.get_selected_item_data(only_one = single_selection)
116 dlg.Destroy()
117
118 if btn_pressed == wx.ID_OK:
119 if can_return_empty and (sels is None):
120 return []
121 return sels
122
123 return None
124
125 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
126
128 """A dialog holding a list and a few buttons to act on the items."""
129
130
131
154
157
160
165
176
179
182
183
184
186 if not self.__ignore_OK_button:
187 self._BTN_ok.SetDefault()
188 self._BTN_ok.Enable(True)
189
190 if self.edit_callback is not None:
191 self._BTN_edit.Enable(True)
192
193 if self.delete_callback is not None:
194 self._BTN_delete.Enable(True)
195
197 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
198 if not self.can_return_empty:
199 self._BTN_cancel.SetDefault()
200 self._BTN_ok.Enable(False)
201 self._BTN_edit.Enable(False)
202 self._BTN_delete.Enable(False)
203
218
235
256
275
294
313
314
315
329
330 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
331
354
355 left_extra_button = property(lambda x:x, _set_left_extra_button)
356
379
380 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
381
404
405 right_extra_button = property(lambda x:x, _set_right_extra_button)
406
408 return self.__new_callback
409
411 if callback is not None:
412 if self.refresh_callback is None:
413 raise ValueError('refresh callback must be set before new callback can be set')
414 if not callable(callback):
415 raise ValueError('<new> callback is not a callable: %s' % callback)
416 self.__new_callback = callback
417
418 if callback is None:
419 self._BTN_new.Enable(False)
420 self._BTN_new.Hide()
421 else:
422 self._BTN_new.Enable(True)
423 self._BTN_new.Show()
424
425 new_callback = property(_get_new_callback, _set_new_callback)
426
428 return self.__edit_callback
429
431 if callback is not None:
432 if not callable(callback):
433 raise ValueError('<edit> callback is not a callable: %s' % callback)
434 self.__edit_callback = callback
435
436 if callback is None:
437 self._BTN_edit.Enable(False)
438 self._BTN_edit.Hide()
439 else:
440 self._BTN_edit.Enable(True)
441 self._BTN_edit.Show()
442
443 edit_callback = property(_get_edit_callback, _set_edit_callback)
444
446 return self.__delete_callback
447
449 if callback is not None:
450 if self.refresh_callback is None:
451 raise ValueError('refresh callback must be set before delete callback can be set')
452 if not callable(callback):
453 raise ValueError('<delete> callback is not a callable: %s' % callback)
454 self.__delete_callback = callback
455
456 if callback is None:
457 self._BTN_delete.Enable(False)
458 self._BTN_delete.Hide()
459 else:
460 self._BTN_delete.Enable(True)
461 self._BTN_delete.Show()
462
463 delete_callback = property(_get_delete_callback, _set_delete_callback)
464
466 return self.__refresh_callback
467
475
477 if callback is not None:
478 if not callable(callback):
479 raise ValueError('<refresh> callback is not a callable: %s' % callback)
480 self.__refresh_callback = callback
481 if callback is not None:
482 wx.CallAfter(self._set_refresh_callback_helper)
483
484 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
485
488
489 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
490
491
492
494 if message is None:
495 self._LBL_message.Hide()
496 return
497 self._LBL_message.SetLabel(message)
498 self._LBL_message.Show()
499
500 message = property(lambda x:x, _set_message)
501
502 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
503
505 """A panel holding a generic multi-column list and action buttions."""
506
532
533
534
537
547
550
553
556
557
558
560 if self.edit_callback is not None:
561 self._BTN_edit.Enable(True)
562 if self.delete_callback is not None:
563 self._BTN_remove.Enable(True)
564 if self.__select_callback is not None:
565 item = self._LCTRL_items.get_selected_item_data(only_one=True)
566 self.__select_callback(item)
567
569 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
570 self._BTN_edit.Enable(False)
571 self._BTN_remove.Enable(False)
572 if self.__select_callback is not None:
573 self.__select_callback(None)
574
585
590
604
618
634
650
666
667
668
670 return self.__new_callback
671
673 if callback is not None:
674 if not callable(callback):
675 raise ValueError('<new> callback is not a callable: %s' % callback)
676 self.__new_callback = callback
677 self._BTN_add.Enable(callback is not None)
678
679 new_callback = property(_get_new_callback, _set_new_callback)
680
682 return self.__select_callback
683
685 if callback is not None:
686 if not callable(callback):
687 raise ValueError('<select> callback is not a callable: %s' % callback)
688 self.__select_callback = callback
689
690 select_callback = property(_get_select_callback, _set_select_callback)
691
693 return self._LBL_message.GetLabel()
694
696 if msg is None:
697 self._LBL_message.Hide()
698 self._LBL_message.SetLabel(u'')
699 else:
700 self._LBL_message.SetLabel(msg)
701 self._LBL_message.Show()
702 self.Layout()
703
704 message = property(_get_message, _set_message)
705
721
722 left_extra_button = property(lambda x:x, _set_left_extra_button)
723
739
740 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
741
757
758 right_extra_button = property(lambda x:x, _set_right_extra_button)
759
760 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
761
763
765
766 try:
767 msg = kwargs['msg']
768 del kwargs['msg']
769 except KeyError:
770 msg = None
771
772 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
773
774 if msg is None:
775 self._LBL_msg.Hide()
776 else:
777 self._LBL_msg.SetLabel(msg)
778
779 self.allow_duplicate_picks = True
780
781 self._LCTRL_left.activate_callback = self.__pick_selected
782 self.__extra_button_callback = None
783
784 self._LCTRL_left.SetFocus()
785
786
787
788 - def set_columns(self, columns=None, columns_right=None):
789 self._LCTRL_left.set_columns(columns = columns)
790 if columns_right is None:
791 self._LCTRL_right.set_columns(columns = columns)
792 else:
793 if len(columns_right) < len(columns):
794 cols = columns
795 else:
796 cols = columns_right[:len(columns)]
797 self._LCTRL_right.set_columns(columns = cols)
798
806
809
814
820
823
826
827 picks = property(get_picks, lambda x:x)
828
844
845 extra_button = property(lambda x:x, _set_extra_button)
846
847
848
850 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
851 return
852
853 right_items = self._LCTRL_right.get_string_items()
854 right_data = self._LCTRL_right.get_item_data()
855 if right_data is None:
856 right_data = []
857
858 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
859 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
860
861 if self.allow_duplicate_picks:
862 right_items.extend(selected_items)
863 right_data.extend(selected_data)
864 self._LCTRL_right.set_string_items(items = right_items)
865 self._LCTRL_right.set_data(data = right_data)
866 self._LCTRL_right.set_column_widths()
867
868
869 return
870
871 for sel_item, sel_data in zip(selected_items, selected_data):
872 if sel_item in right_items:
873 continue
874 right_items.append(sel_item)
875 right_data.append(sel_data)
876 self._LCTRL_right.set_string_items(items = right_items)
877 self._LCTRL_right.set_data(data = right_data)
878 self._LCTRL_right.set_column_widths()
879
880
881
883 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
884 return
885
886 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
887 self._LCTRL_right.remove_item(item_idx)
888
889 if self._LCTRL_right.GetItemCount() == 0:
890 self._BTN_right2left.Enable(False)
891
892
893
894
895
896
898 self._BTN_left2right.Enable(True)
899
901 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
902 self._BTN_left2right.Enable(False)
903
905 self._BTN_right2left.Enable(True)
906
908 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
909 self._BTN_right2left.Enable(False)
910
913
916
919
922
923 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
924
927
928 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
929
930
931 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin):
932
933
934
935
936
937
938
939
940 map_item_idx2data_idx = wx.ListCtrl.GetItemData
941
942 sort_order_tags = {
943 True: u' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
944 False: u' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
945 }
946
948
949 self.debug = None
950
951 try:
952 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
953 except KeyError:
954 kwargs['style'] = wx.LC_REPORT
955
956 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
957
958 wx.ListCtrl.__init__(self, *args, **kwargs)
959 listmixins.ListCtrlAutoWidthMixin.__init__(self)
960
961
962 self._invalidate_sorting_metadata()
963 listmixins.ColumnSorterMixin.__init__(self, 0)
964 self.__secondary_sort_col = None
965
966
967
968 self.__widths = None
969 self.__data = None
970 self.__activate_callback = None
971 self.__rightclick_callback = None
972
973 self.__item_tooltip_callback = None
974 self.__tt_last_item = None
975 self.__tt_static_part = _("""Select the items you want to work on.
976
977 A discontinuous selection may depend on your holding down a platform-dependent modifier key (<ctrl>, <alt>, etc) or key combination (eg. <ctrl-shift> or <ctrl-alt>) while clicking.""")
978 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
979
980 self.__next_line_to_search = 0
981 self.__search_data = None
982 self.__search_dlg = None
983 self.__searchable_cols = None
984
985 self.Bind(wx.EVT_CHAR, self._on_char)
986 self.Bind(wx.EVT_FIND_CLOSE, self._on_search_dlg_closed)
987 self.Bind(wx.EVT_FIND, self._on_search_first_match)
988 self.Bind(wx.EVT_FIND_NEXT, self._on_search_next_match)
989
990
991
993 """(Re)define the columns.
994
995 Note that this will (have to) delete the items.
996 """
997 self.ClearAll()
998 self.__tt_last_item = None
999 if columns is None:
1000 return
1001 for idx in range(len(columns)):
1002 self.InsertColumn(idx, columns[idx])
1003
1004 self._invalidate_sorting_metadata()
1005
1007 """Set the column width policy.
1008
1009 widths = None:
1010 use previous policy if any or default policy
1011 widths != None:
1012 use this policy and remember it for later calls
1013
1014 This means there is no way to *revert* to the default policy :-(
1015 """
1016
1017 if widths is not None:
1018 self.__widths = widths
1019 for idx in range(len(self.__widths)):
1020 self.SetColumnWidth(col = idx, width = self.__widths[idx])
1021 return
1022
1023
1024 if self.__widths is not None:
1025 for idx in range(len(self.__widths)):
1026 self.SetColumnWidth(col = idx, width = self.__widths[idx])
1027 return
1028
1029
1030 if self.GetItemCount() == 0:
1031 width_type = wx.LIST_AUTOSIZE_USEHEADER
1032 else:
1033 width_type = wx.LIST_AUTOSIZE
1034 for idx in range(self.GetColumnCount()):
1035 self.SetColumnWidth(col = idx, width = width_type)
1036
1038 """All item members must be unicode()able or None."""
1039
1040 wx.BeginBusyCursor()
1041 self._invalidate_sorting_metadata()
1042
1043
1044 loop = 0
1045 while True:
1046 if loop > 3:
1047 _log.debug('unable to delete list items after looping 3 times, continuing and hoping for the best')
1048 break
1049 loop += 1
1050 if self.debug is not None:
1051 _log.debug('[round %s] GetItemCount() before DeleteAllItems(): %s (%s, thread [%s])', loop, self.GetItemCount(), self.debug, thread.get_ident())
1052 if not self.DeleteAllItems():
1053 _log.debug('DeleteAllItems() failed (%s)', self.debug)
1054 item_count = self.GetItemCount()
1055 if self.debug is not None:
1056 _log.debug('GetItemCount() after DeleteAllItems(): %s (%s)', item_count, self.debug)
1057 if item_count == 0:
1058 break
1059 wx.SafeYield(None, True)
1060 _log.debug('GetItemCount() not 0 after DeleteAllItems() (%s)', self.debug)
1061 time.sleep(0.3)
1062 wx.SafeYield(None, True)
1063
1064 if items is None:
1065 self.data = None
1066 wx.EndBusyCursor()
1067 return
1068
1069
1070 for item in items:
1071 try:
1072 item[0]
1073 if not isinstance(item, basestring):
1074 is_numerically_iterable = True
1075
1076 else:
1077 is_numerically_iterable = False
1078 except TypeError:
1079 is_numerically_iterable = False
1080
1081 if is_numerically_iterable:
1082
1083
1084 col_val = unicode(item[0])
1085 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1086 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1087 col_val = unicode(item[col_num])
1088 self.SetStringItem(index = row_num, col = col_num, label = col_val)
1089 else:
1090
1091 col_val = unicode(item)
1092 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1093
1094
1095 self.data = items
1096
1097 wx.EndBusyCursor()
1098
1100 """<data> assumed to be a list corresponding to the item indices"""
1101 if data is not None:
1102 item_count = self.GetItemCount()
1103 if len(data) != item_count:
1104 _log.debug('<data> length (%s) must be equal to number of list items (%s) (%s, thread [%s])', len(data), item_count, self.debug, thread.get_ident())
1105 for item_idx in range(len(data)):
1106 self.SetItemData(item_idx, item_idx)
1107 self.__data = data
1108 self.__tt_last_item = None
1109
1110
1111 return
1112
1117
1118 data = property(_get_data, set_data)
1119
1126
1128 if self.__is_single_selection:
1129 return [self.GetFirstSelected()]
1130 selections = []
1131 idx = self.GetFirstSelected()
1132 while idx != -1:
1133 selections.append(idx)
1134 idx = self.GetNextSelected(idx)
1135 return selections
1136
1137 selections = property(__get_selections, set_selections)
1138
1139
1140
1142 labels = []
1143 for col_idx in self.GetColumnCount():
1144 col = self.GetColumn(col = col_idx)
1145 labels.append(col.GetText())
1146 return labels
1147
1149 if item_idx is not None:
1150 return self.GetItem(item_idx)
1151
1153 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
1154
1156 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
1157
1159
1160 if self.__is_single_selection or only_one:
1161 return self.GetFirstSelected()
1162
1163 items = []
1164 idx = self.GetFirstSelected()
1165 while idx != -1:
1166 items.append(idx)
1167 idx = self.GetNextSelected(idx)
1168
1169 return items
1170
1172
1173 if self.__is_single_selection or only_one:
1174 return self.GetItemText(self.GetFirstSelected())
1175
1176 items = []
1177 idx = self.GetFirstSelected()
1178 while idx != -1:
1179 items.append(self.GetItemText(idx))
1180 idx = self.GetNextSelected(idx)
1181
1182 return items
1183
1185 if self.__data is None:
1186 return None
1187
1188 if item_idx is not None:
1189 return self.__data[self.map_item_idx2data_idx(item_idx)]
1190
1191
1192
1193
1194 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1195
1197
1198 if self.__is_single_selection or only_one:
1199 if self.__data is None:
1200 return None
1201 idx = self.GetFirstSelected()
1202 if idx == -1:
1203 return None
1204 return self.__data[self.map_item_idx2data_idx(idx)]
1205
1206 data = []
1207 if self.__data is None:
1208 return data
1209 idx = self.GetFirstSelected()
1210 while idx != -1:
1211 data.append(self.__data[self.map_item_idx2data_idx(idx)])
1212 idx = self.GetNextSelected(idx)
1213
1214 return data
1215
1217 self.Select(idx = self.GetFirstSelected(), on = 0)
1218
1220
1221
1222
1223
1224
1225
1226 self.DeleteItem(item_idx)
1227 self.__tt_last_item = None
1228 self._invalidate_sorting_metadata()
1229
1230
1231
1233 event.Skip()
1234 if self.__activate_callback is not None:
1235 self.__activate_callback(event)
1236
1238 event.Skip()
1239 if self.__rightclick_callback is not None:
1240 self.__rightclick_callback(event)
1241
1243
1244 if evt.GetModifiers() != wx.MOD_CMD:
1245 evt.Skip()
1246 return
1247
1248 if unichr(evt.GetRawKeyCode()) != u'f':
1249 evt.Skip()
1250 return
1251
1252 if self.__search_dlg is not None:
1253 self.__search_dlg.Close()
1254 return
1255
1256 if self.__searchable_cols is None:
1257 self.searchable_columns = None
1258
1259 if len(self.__searchable_cols) == 0:
1260 return
1261
1262 if self.__search_data is None:
1263 self.__search_data = wx.FindReplaceData()
1264 self.__search_dlg = wx.FindReplaceDialog (
1265 self,
1266 self.__search_data,
1267 _('Search in list'),
1268 wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD
1269 )
1270 self.__search_dlg.Show(True)
1271
1273 """Update tooltip on mouse motion.
1274
1275 for s in dir(wx):
1276 if s.startswith('LIST_HITTEST'):
1277 print s, getattr(wx, s)
1278
1279 LIST_HITTEST_ABOVE 1
1280 LIST_HITTEST_BELOW 2
1281 LIST_HITTEST_NOWHERE 4
1282 LIST_HITTEST_ONITEM 672
1283 LIST_HITTEST_ONITEMICON 32
1284 LIST_HITTEST_ONITEMLABEL 128
1285 LIST_HITTEST_ONITEMRIGHT 256
1286 LIST_HITTEST_ONITEMSTATEICON 512
1287 LIST_HITTEST_TOLEFT 1024
1288 LIST_HITTEST_TORIGHT 2048
1289 """
1290 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
1291
1292
1293 if where_flag not in [
1294 wx.LIST_HITTEST_ONITEMLABEL,
1295 wx.LIST_HITTEST_ONITEMICON,
1296 wx.LIST_HITTEST_ONITEMSTATEICON,
1297 wx.LIST_HITTEST_ONITEMRIGHT,
1298 wx.LIST_HITTEST_ONITEM
1299 ]:
1300 self.__tt_last_item = None
1301 self.SetToolTipString(self.__tt_static_part)
1302 return
1303
1304
1305 if self.__tt_last_item == item_idx:
1306 return
1307
1308
1309 self.__tt_last_item = item_idx
1310
1311
1312
1313 if item_idx == wx.NOT_FOUND:
1314 self.SetToolTipString(self.__tt_static_part)
1315 return
1316
1317
1318 if self.__data is None:
1319 self.SetToolTipString(self.__tt_static_part)
1320 return
1321
1322
1323
1324
1325
1326 if (
1327 (item_idx > (len(self.__data) - 1))
1328 or
1329 (item_idx < -1)
1330 ):
1331 self.SetToolTipString(self.__tt_static_part)
1332 print "*************************************************************"
1333 print "GNUmed has detected an inconsistency with list item tooltips."
1334 print ""
1335 print "This is not a big problem and you can keep working."
1336 print ""
1337 print "However, please send us the following so we can fix GNUmed:"
1338 print ""
1339 print "item idx: %s" % item_idx
1340 print 'where flag: %s' % where_flag
1341 print 'data list length: %s' % len(self.__data)
1342 print "*************************************************************"
1343 return
1344
1345 dyna_tt = None
1346 if self.__item_tooltip_callback is not None:
1347 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
1348
1349 if dyna_tt is None:
1350 self.SetToolTipString(self.__tt_static_part)
1351 return
1352
1353 self.SetToolTipString(dyna_tt)
1354
1355
1356
1358 self.__search_dlg.Destroy()
1359 self.__search_dlg = None
1360
1361
1362
1363
1364
1365
1366
1367
1368
1370 for row_idx in range(self.__next_line_to_search, self.ItemCount):
1371 for col_idx in range(self.ColumnCount):
1372 if col_idx not in self.__searchable_cols:
1373 continue
1374 col_val = self.GetItem(row_idx, col_idx).GetText()
1375 if regex.search(search_term, col_val, regex.U | regex.I) is not None:
1376 self.Select(row_idx)
1377 self.EnsureVisible(row_idx)
1378 if row_idx == self.ItemCount - 1:
1379
1380 self.__next_line_to_search = 0
1381 else:
1382 self.__next_line_to_search = row_idx + 1
1383 return True
1384
1385 self.__next_line_to_search = 0
1386 return False
1387
1389 self.__on_search_match(evt.GetFindString())
1390
1392 self.__on_search_match(evt.GetFindString())
1393
1394
1395
1397 return self.__activate_callback
1398
1400 if callback is None:
1401 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1402 else:
1403 if not callable(callback):
1404 raise ValueError('<activate> callback is not a callable: %s' % callback)
1405 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1406 self.__activate_callback = callback
1407
1408 activate_callback = property(_get_activate_callback, _set_activate_callback)
1409
1411 return self.__rightclick_callback
1412
1414 if callback is None:
1415 self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK)
1416 else:
1417 if not callable(callback):
1418 raise ValueError('<rightclick> callback is not a callable: %s' % callback)
1419 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
1420 self.__rightclick_callback = callback
1421
1422 rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback)
1423
1429
1430
1431
1432
1433
1434 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1435
1437
1438 if cols is None:
1439 self.__searchable_cols = range(self.ColumnCount)
1440 return
1441
1442
1443 new_cols = {}
1444 for col in cols:
1445 if col < self.ColumnCount:
1446 new_cols[col] = True
1447 self.__searchable_cols = new_cols.keys()
1448
1449 searchable_columns = property(lambda x:x, _set_searchable_cols)
1450
1451
1452
1454 if self.itemDataMap is None:
1455 self._update_sorting_metadata()
1456 return self
1457
1459 self._cleanup_column_headers()
1460
1461 col_idx, is_ascending = self.GetSortState()
1462 col_state = self.GetColumn(col_idx)
1463 col_state.m_text += self.sort_order_tags[is_ascending]
1464 self.SetColumn(col_idx, col_state)
1465
1467 if self.__secondary_sort_col is None:
1468 return (item1_idx, item2_idx)
1469 if self.__secondary_sort_col == primary_sort_col:
1470 return (item1_idx, item2_idx)
1471 val1 = self.itemDataMap[item1_idx][self.__secondary_sort_col]
1472 val2 = self.itemDataMap[item2_idx][self.__secondary_sort_col]
1473 order = cmp(val1, val2)
1474 if order == 0:
1475 return (item1_idx, item2_idx)
1476
1477 currently_ascending = self._colSortFlag[primary_sort_col]
1478 if currently_ascending:
1479 val1, val2 = min(val1, val2), max(val1, val2)
1480 else:
1481 val1, val2 = max(val1, val2), min(val1, val2)
1482 return (val1, val2)
1483
1485 dict2sort = {}
1486 item_count = self.GetItemCount()
1487 if item_count == 0:
1488 return dict2sort
1489 col_count = self.GetColumnCount()
1490 for item_idx in range(item_count):
1491 dict2sort[item_idx] = ()
1492 if col_count == 0:
1493 continue
1494 for col_idx in range(col_count):
1495 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
1496
1497 return dict2sort
1498
1500 for col_idx in range(self.ColumnCount):
1501 col_state = self.GetColumn(col_idx)
1502 if col_state.m_text.endswith(self.sort_order_tags[True]):
1503 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[True])]
1504 if col_state.m_text.endswith(self.sort_order_tags[False]):
1505 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[False])]
1506 self.SetColumn(col_idx, col_state)
1507
1512
1516
1518
1519
1520
1521
1522
1523
1524 event.Skip()
1525
1527 return self.__secondary_sort_col
1528
1530 if col is None:
1531 self.__secondary_sort_col = None
1532 return
1533 if col > self.GetColumnCount():
1534 raise ValueError('cannot secondary-sort on col [%s], there are only [%s] columns', col, self.GetColumnCount())
1535 self.__secondary_sort_col = col
1536
1537 secondary_sort_column = property(__get_secondary_sort_col, __set_secondary_sort_col)
1538
1539
1540
1541
1542 if __name__ == '__main__':
1543
1544 if len(sys.argv) < 2:
1545 sys.exit()
1546
1547 if sys.argv[1] != 'test':
1548 sys.exit()
1549
1550 sys.path.insert(0, '../../')
1551
1552 from Gnumed.pycommon import gmI18N
1553 gmI18N.activate_locale()
1554 gmI18N.install_domain()
1555
1556
1558 app = wx.PyWidgetTester(size = (400, 500))
1559 dlg = wx.MultiChoiceDialog (
1560 parent = None,
1561 message = 'test message',
1562 caption = 'test caption',
1563 choices = ['a', 'b', 'c', 'd', 'e']
1564 )
1565 dlg.ShowModal()
1566 sels = dlg.GetSelections()
1567 print "selected:"
1568 for sel in sels:
1569 print sel
1570
1572
1573 def edit(argument):
1574 print "editor called with:"
1575 print argument
1576
1577 def refresh(lctrl):
1578 choices = ['a', 'b', 'c']
1579 lctrl.set_string_items(choices)
1580
1581 app = wx.PyWidgetTester(size = (200, 50))
1582 chosen = get_choices_from_list (
1583
1584 caption = 'select health issues',
1585
1586
1587 columns = ['issue'],
1588 refresh_callback = refresh
1589
1590 )
1591 print "chosen:"
1592 print chosen
1593
1595 app = wx.PyWidgetTester(size = (200, 50))
1596 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1597 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1598
1599 dlg.set_string_items(['patient', 'emr', 'docs'])
1600 result = dlg.ShowModal()
1601 print result
1602 print dlg.get_picks()
1603
1604
1605
1606 test_item_picker_dlg()
1607
1608
1609
1610