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
15 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
16 __license__ = "GPL v2 or later"
17
18
19 import sys, types
20
21
22 import wx
23 import wx.lib.mixins.listctrl as listmixins
24
25
26 if __name__ == '__main__':
27 sys.path.insert(0, '../../')
28
29
30
31
32 -def get_choices_from_list (
33 parent=None,
34 msg=None,
35 caption=None,
36 choices=None,
37 selections=None,
38 columns=None,
39 data=None,
40 edit_callback=None,
41 new_callback=None,
42 delete_callback=None,
43 refresh_callback=None,
44 single_selection=False,
45 can_return_empty=False,
46 ignore_OK_button=False,
47 left_extra_button=None,
48 middle_extra_button=None,
49 right_extra_button=None,
50 list_tooltip_callback=None):
115
116 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
117
119 """A dialog holding a list and a few buttons to act on the items."""
120
121
122
145
148
151
156
167
170
173
174
175
177 if not self.__ignore_OK_button:
178 self._BTN_ok.SetDefault()
179 self._BTN_ok.Enable(True)
180
181 if self.edit_callback is not None:
182 self._BTN_edit.Enable(True)
183
184 if self.delete_callback is not None:
185 self._BTN_delete.Enable(True)
186
188 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
189 if not self.can_return_empty:
190 self._BTN_cancel.SetDefault()
191 self._BTN_ok.Enable(False)
192 self._BTN_edit.Enable(False)
193 self._BTN_delete.Enable(False)
194
209
226
247
263
279
295
296
297
311
312 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
313
329
330 left_extra_button = property(lambda x:x, _set_left_extra_button)
331
347
348 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
349
365
366 right_extra_button = property(lambda x:x, _set_right_extra_button)
367
369 return self.__new_callback
370
372 if callback is not None:
373 if self.refresh_callback is None:
374 raise ValueError('refresh callback must be set before new callback can be set')
375 if not callable(callback):
376 raise ValueError('<new> callback is not a callable: %s' % callback)
377 self.__new_callback = callback
378
379 if callback is None:
380 self._BTN_new.Enable(False)
381 self._BTN_new.Hide()
382 else:
383 self._BTN_new.Enable(True)
384 self._BTN_new.Show()
385
386 new_callback = property(_get_new_callback, _set_new_callback)
387
389 return self.__edit_callback
390
392 if callback is not None:
393 if not callable(callback):
394 raise ValueError('<edit> callback is not a callable: %s' % callback)
395 self.__edit_callback = callback
396
397 if callback is None:
398 self._BTN_edit.Enable(False)
399 self._BTN_edit.Hide()
400 else:
401 self._BTN_edit.Enable(True)
402 self._BTN_edit.Show()
403
404 edit_callback = property(_get_edit_callback, _set_edit_callback)
405
407 return self.__delete_callback
408
410 if callback is not None:
411 if self.refresh_callback is None:
412 raise ValueError('refresh callback must be set before delete callback can be set')
413 if not callable(callback):
414 raise ValueError('<delete> callback is not a callable: %s' % callback)
415 self.__delete_callback = callback
416
417 if callback is None:
418 self._BTN_delete.Enable(False)
419 self._BTN_delete.Hide()
420 else:
421 self._BTN_delete.Enable(True)
422 self._BTN_delete.Show()
423
424 delete_callback = property(_get_delete_callback, _set_delete_callback)
425
427 return self.__refresh_callback
428
436
438 if callback is not None:
439 if not callable(callback):
440 raise ValueError('<refresh> callback is not a callable: %s' % callback)
441 self.__refresh_callback = callback
442 if callback is not None:
443 wx.CallAfter(self._set_refresh_callback_helper)
444
445 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
446
449
450 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
451
452
453
455 if message is None:
456 self._LBL_message.Hide()
457 return
458 self._LBL_message.SetLabel(message)
459 self._LBL_message.Show()
460
461 message = property(lambda x:x, _set_message)
462
463 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
464
466 """A panel holding a generic multi-column list and action buttions."""
467
493
494
495
498
500 self._LCTRL_items.set_string_items(items = items)
501 self._LCTRL_items.set_column_widths()
502
503 if (items is None) or (len(items) == 0):
504 self._BTN_edit.Enable(False)
505 self._BTN_remove.Enable(False)
506 else:
507 self._LCTRL_items.Select(0)
508
511
514
517
518
519
521 if self.edit_callback is not None:
522 self._BTN_edit.Enable(True)
523 if self.delete_callback is not None:
524 self._BTN_remove.Enable(True)
525 if self.__select_callback is not None:
526 item = self._LCTRL_items.get_selected_item_data(only_one=True)
527 self.__select_callback(item)
528
530 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
531 self._BTN_edit.Enable(False)
532 self._BTN_remove.Enable(False)
533 if self.__select_callback is not None:
534 self.__select_callback(None)
535
546
548 if self.edit_callback is None:
549 return
550 self._on_edit_button_pressed(event)
551
565
579
595
611
627
628
629
631 return self.__new_callback
632
634 if callback is not None:
635 if not callable(callback):
636 raise ValueError('<new> callback is not a callable: %s' % callback)
637 self.__new_callback = callback
638 self._BTN_add.Enable(callback is not None)
639
640 new_callback = property(_get_new_callback, _set_new_callback)
641
643 return self.__select_callback
644
646 if callback is not None:
647 if not callable(callback):
648 raise ValueError('<select> callback is not a callable: %s' % callback)
649 self.__select_callback = callback
650
651 select_callback = property(_get_select_callback, _set_select_callback)
652
654 return self._LBL_message.GetLabel()
655
657 if msg is None:
658 self._LBL_message.Hide()
659 self._LBL_message.SetLabel(u'')
660 else:
661 self._LBL_message.SetLabel(msg)
662 self._LBL_message.Show()
663 self.Layout()
664
665 message = property(_get_message, _set_message)
666
682
683 left_extra_button = property(lambda x:x, _set_left_extra_button)
684
700
701 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
702
718
719 right_extra_button = property(lambda x:x, _set_right_extra_button)
720
721 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
722
724
726
727 try:
728 msg = kwargs['msg']
729 del kwargs['msg']
730 except KeyError:
731 msg = None
732
733 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
734
735 if msg is None:
736 self._LBL_msg.Hide()
737 else:
738 self._LBL_msg.SetLabel(msg)
739
740 self._LCTRL_left.activate_callback = self.__pick_selected
741
742 self.__extra_button_callback = None
743
744 self._LCTRL_left.SetFocus()
745
746
747
748 - def set_columns(self, columns=None, columns_right=None):
749 self._LCTRL_left.set_columns(columns = columns)
750 if columns_right is None:
751 self._LCTRL_right.set_columns(columns = columns)
752 else:
753 if len(columns_right) < len(columns):
754 cols = columns
755 else:
756 cols = columns_right[:len(columns)]
757 self._LCTRL_right.set_columns(columns = cols)
758
766
769
774
780
783
786
787 picks = property(get_picks, lambda x:x)
788
804
805 extra_button = property(lambda x:x, _set_extra_button)
806
807
808
828
830 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
831 return
832
833 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
834 self._LCTRL_right.remove_item(item_idx)
835
836 if self._LCTRL_right.GetItemCount() == 0:
837 self._BTN_right2left.Enable(False)
838
839
840
842 self._BTN_left2right.Enable(True)
843
845 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
846 self._BTN_left2right.Enable(False)
847
849 self._BTN_right2left.Enable(True)
850
852 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
853 self._BTN_right2left.Enable(False)
854
857
860
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
879
880
881
883
884 try:
885 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
886 except KeyError:
887 kwargs['style'] = wx.LC_REPORT
888
889 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
890
891 wx.ListCtrl.__init__(self, *args, **kwargs)
892 listmixins.ListCtrlAutoWidthMixin.__init__(self)
893
894 self.__widths = None
895 self.__data = None
896 self.__activate_callback = None
897 self.__rightclick_callback = None
898
899 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
900 self.__item_tooltip_callback = None
901 self.__tt_last_item = None
902 self.__tt_static_part = _("""Select the items you want to work on.
903
904 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.""")
905
906
907
909 """(Re)define the columns.
910
911 Note that this will (have to) delete the items.
912 """
913 self.ClearAll()
914 self.__tt_last_item = None
915 if columns is None:
916 return
917 for idx in range(len(columns)):
918 self.InsertColumn(idx, columns[idx])
919
921 """Set the column width policy.
922
923 widths = None:
924 use previous policy if any or default policy
925 widths != None:
926 use this policy and remember it for later calls
927
928 This means there is no way to *revert* to the default policy :-(
929 """
930
931 if widths is not None:
932 self.__widths = widths
933 for idx in range(len(self.__widths)):
934 self.SetColumnWidth(col = idx, width = self.__widths[idx])
935 return
936
937
938 if self.__widths is not None:
939 for idx in range(len(self.__widths)):
940 self.SetColumnWidth(col = idx, width = self.__widths[idx])
941 return
942
943
944 if self.GetItemCount() == 0:
945 width_type = wx.LIST_AUTOSIZE_USEHEADER
946 else:
947 width_type = wx.LIST_AUTOSIZE
948 for idx in range(self.GetColumnCount()):
949 self.SetColumnWidth(col = idx, width = width_type)
950
952 """All item members must be unicode()able or None."""
953
954 self.DeleteAllItems()
955 self.__data = items
956 self.__tt_last_item = None
957
958 if items is None:
959 return
960
961 for item in items:
962 try:
963 item[0]
964 if not isinstance(item, basestring):
965 is_numerically_iterable = True
966 else:
967 is_numerically_iterable = False
968 except TypeError:
969 is_numerically_iterable = False
970
971 if is_numerically_iterable:
972
973
974 col_val = unicode(item[0])
975 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
976 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
977 col_val = unicode(item[col_idx])
978 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
979 else:
980
981 col_val = unicode(item)
982 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
983
985 """<data must be a list corresponding to the item indices>"""
986 self.__data = data
987 self.__tt_last_item = None
988
995
996
998 if self.__is_single_selection:
999 return [self.GetFirstSelected()]
1000 selections = []
1001 idx = self.GetFirstSelected()
1002 while idx != -1:
1003 selections.append(idx)
1004 idx = self.GetNextSelected(idx)
1005 return selections
1006
1007 selections = property(__get_selections, set_selections)
1008
1009
1010
1012 labels = []
1013 for col_idx in self.GetColumnCount():
1014 col = self.GetColumn(col = col_idx)
1015 labels.append(col.GetText())
1016 return labels
1017
1019 if item_idx is not None:
1020 return self.GetItem(item_idx)
1021
1023 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
1024
1026 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
1027
1029
1030 if self.__is_single_selection or only_one:
1031 return self.GetFirstSelected()
1032
1033 items = []
1034 idx = self.GetFirstSelected()
1035 while idx != -1:
1036 items.append(idx)
1037 idx = self.GetNextSelected(idx)
1038
1039 return items
1040
1042
1043 if self.__is_single_selection or only_one:
1044 return self.GetItemText(self.GetFirstSelected())
1045
1046 items = []
1047 idx = self.GetFirstSelected()
1048 while idx != -1:
1049 items.append(self.GetItemText(idx))
1050 idx = self.GetNextSelected(idx)
1051
1052 return items
1053
1055 if self.__data is None:
1056 return None
1057
1058 if item_idx is not None:
1059 return self.__data[item_idx]
1060
1061 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
1062
1064
1065 if self.__is_single_selection or only_one:
1066 if self.__data is None:
1067 return None
1068 idx = self.GetFirstSelected()
1069 if idx == -1:
1070 return None
1071 return self.__data[idx]
1072
1073 data = []
1074 if self.__data is None:
1075 return data
1076 idx = self.GetFirstSelected()
1077 while idx != -1:
1078 data.append(self.__data[idx])
1079 idx = self.GetNextSelected(idx)
1080
1081 return data
1082
1084 self.Select(idx = self.GetFirstSelected(), on = 0)
1085
1087 self.DeleteItem(item_idx)
1088 if self.__data is not None:
1089 del self.__data[item_idx]
1090 self.__tt_last_item = None
1091
1092
1093
1095 event.Skip()
1096 if self.__activate_callback is not None:
1097 self.__activate_callback(event)
1098
1100 event.Skip()
1101 if self.__rightclick_callback is not None:
1102 self.__rightclick_callback(event)
1103
1105 """Update tooltip on mouse motion.
1106
1107 for s in dir(wx):
1108 if s.startswith('LIST_HITTEST'):
1109 print s, getattr(wx, s)
1110
1111 LIST_HITTEST_ABOVE 1
1112 LIST_HITTEST_BELOW 2
1113 LIST_HITTEST_NOWHERE 4
1114 LIST_HITTEST_ONITEM 672
1115 LIST_HITTEST_ONITEMICON 32
1116 LIST_HITTEST_ONITEMLABEL 128
1117 LIST_HITTEST_ONITEMRIGHT 256
1118 LIST_HITTEST_ONITEMSTATEICON 512
1119 LIST_HITTEST_TOLEFT 1024
1120 LIST_HITTEST_TORIGHT 2048
1121 """
1122 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
1123
1124
1125 if where_flag not in [
1126 wx.LIST_HITTEST_ONITEMLABEL,
1127 wx.LIST_HITTEST_ONITEMICON,
1128 wx.LIST_HITTEST_ONITEMSTATEICON,
1129 wx.LIST_HITTEST_ONITEMRIGHT,
1130 wx.LIST_HITTEST_ONITEM
1131 ]:
1132 self.__tt_last_item = None
1133 self.SetToolTipString(self.__tt_static_part)
1134 return
1135
1136
1137 if self.__tt_last_item == item_idx:
1138 return
1139
1140
1141 self.__tt_last_item = item_idx
1142
1143
1144
1145 if item_idx == wx.NOT_FOUND:
1146 self.SetToolTipString(self.__tt_static_part)
1147 return
1148
1149
1150 if self.__data is None:
1151 self.SetToolTipString(self.__tt_static_part)
1152 return
1153
1154
1155
1156
1157
1158 if (
1159 (item_idx > (len(self.__data) - 1))
1160 or
1161 (item_idx < -1)
1162 ):
1163 self.SetToolTipString(self.__tt_static_part)
1164 print "*************************************************************"
1165 print "GNUmed has detected an inconsistency with list item tooltips."
1166 print ""
1167 print "This is not a big problem and you can keep working."
1168 print ""
1169 print "However, please send us the following so we can fix GNUmed:"
1170 print ""
1171 print "item idx: %s" % item_idx
1172 print 'where flag: %s' % where_flag
1173 print 'data list length: %s' % len(self.__data)
1174 print "*************************************************************"
1175 return
1176
1177 dyna_tt = None
1178 if self.__item_tooltip_callback is not None:
1179 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
1180
1181 if dyna_tt is None:
1182 self.SetToolTipString(self.__tt_static_part)
1183 return
1184
1185 self.SetToolTipString(dyna_tt)
1186
1187
1188
1190 return self.__activate_callback
1191
1193 if callback is None:
1194 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1195 else:
1196 if not callable(callback):
1197 raise ValueError('<activate> callback is not a callable: %s' % callback)
1198 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1199 self.__activate_callback = callback
1200
1201 activate_callback = property(_get_activate_callback, _set_activate_callback)
1202
1204 return self.__rightclick_callback
1205
1207 if callback is None:
1208 self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK)
1209 else:
1210 if not callable(callback):
1211 raise ValueError('<rightclick> callback is not a callable: %s' % callback)
1212 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
1213 self.__rightclick_callback = callback
1214
1215 rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback)
1216
1222
1223
1224
1225
1226
1227 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1228
1229
1230
1231 if __name__ == '__main__':
1232
1233 if len(sys.argv) < 2:
1234 sys.exit()
1235
1236 if sys.argv[1] != 'test':
1237 sys.exit()
1238
1239 from Gnumed.pycommon import gmI18N
1240 gmI18N.activate_locale()
1241 gmI18N.install_domain()
1242
1243
1245 app = wx.PyWidgetTester(size = (400, 500))
1246 dlg = wx.MultiChoiceDialog (
1247 parent = None,
1248 message = 'test message',
1249 caption = 'test caption',
1250 choices = ['a', 'b', 'c', 'd', 'e']
1251 )
1252 dlg.ShowModal()
1253 sels = dlg.GetSelections()
1254 print "selected:"
1255 for sel in sels:
1256 print sel
1257
1259
1260 def edit(argument):
1261 print "editor called with:"
1262 print argument
1263
1264 def refresh(lctrl):
1265 choices = ['a', 'b', 'c']
1266 lctrl.set_string_items(choices)
1267
1268 app = wx.PyWidgetTester(size = (200, 50))
1269 chosen = get_choices_from_list (
1270
1271 caption = 'select health issues',
1272
1273
1274 columns = ['issue'],
1275 refresh_callback = refresh
1276
1277 )
1278 print "chosen:"
1279 print chosen
1280
1282 app = wx.PyWidgetTester(size = (200, 50))
1283 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1284 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1285
1286 dlg.set_string_items(['patient', 'emr', 'docs'])
1287 result = dlg.ShowModal()
1288 print result
1289 print dlg.get_picks()
1290
1291
1292
1293 test_item_picker_dlg()
1294
1295
1296
1297