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 __version__ = "$Revision: 1.37 $"
16 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
17 __license__ = "GPL v2 or later"
18
19
20 import sys, types
21
22
23 import wx
24 import wx.lib.mixins.listctrl as listmixins
25
26
27 if __name__ == '__main__':
28 sys.path.insert(0, '../../')
29
30
31
32
33 -def get_choices_from_list (
34 parent=None,
35 msg=None,
36 caption=None,
37 choices=None,
38 selections=None,
39 columns=None,
40 data=None,
41 edit_callback=None,
42 new_callback=None,
43 delete_callback=None,
44 refresh_callback=None,
45 single_selection=False,
46 can_return_empty=False,
47 ignore_OK_button=False,
48 left_extra_button=None,
49 middle_extra_button=None,
50 right_extra_button=None,
51 list_tooltip_callback=None):
116
117 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
118
120 """A dialog holding a list and a few buttons to act on the items."""
121
122
123
146
149
152
157
168
171
174
175
176
178 if not self.__ignore_OK_button:
179 self._BTN_ok.SetDefault()
180 self._BTN_ok.Enable(True)
181
182 if self.edit_callback is not None:
183 self._BTN_edit.Enable(True)
184
185 if self.delete_callback is not None:
186 self._BTN_delete.Enable(True)
187
189 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
190 if not self.can_return_empty:
191 self._BTN_cancel.SetDefault()
192 self._BTN_ok.Enable(False)
193 self._BTN_edit.Enable(False)
194 self._BTN_delete.Enable(False)
195
210
227
248
264
280
296
297
298
312
313 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
314
330
331 left_extra_button = property(lambda x:x, _set_left_extra_button)
332
348
349 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
350
366
367 right_extra_button = property(lambda x:x, _set_right_extra_button)
368
370 return self.__new_callback
371
373 if callback is not None:
374 if self.refresh_callback is None:
375 raise ValueError('refresh callback must be set before new callback can be set')
376 if not callable(callback):
377 raise ValueError('<new> callback is not a callable: %s' % callback)
378 self.__new_callback = callback
379
380 if callback is None:
381 self._BTN_new.Enable(False)
382 self._BTN_new.Hide()
383 else:
384 self._BTN_new.Enable(True)
385 self._BTN_new.Show()
386
387 new_callback = property(_get_new_callback, _set_new_callback)
388
390 return self.__edit_callback
391
393 if callback is not None:
394 if not callable(callback):
395 raise ValueError('<edit> callback is not a callable: %s' % callback)
396 self.__edit_callback = callback
397
398 if callback is None:
399 self._BTN_edit.Enable(False)
400 self._BTN_edit.Hide()
401 else:
402 self._BTN_edit.Enable(True)
403 self._BTN_edit.Show()
404
405 edit_callback = property(_get_edit_callback, _set_edit_callback)
406
408 return self.__delete_callback
409
411 if callback is not None:
412 if self.refresh_callback is None:
413 raise ValueError('refresh callback must be set before delete callback can be set')
414 if not callable(callback):
415 raise ValueError('<delete> callback is not a callable: %s' % callback)
416 self.__delete_callback = callback
417
418 if callback is None:
419 self._BTN_delete.Enable(False)
420 self._BTN_delete.Hide()
421 else:
422 self._BTN_delete.Enable(True)
423 self._BTN_delete.Show()
424
425 delete_callback = property(_get_delete_callback, _set_delete_callback)
426
428 return self.__refresh_callback
429
437
439 if callback is not None:
440 if not callable(callback):
441 raise ValueError('<refresh> callback is not a callable: %s' % callback)
442 self.__refresh_callback = callback
443 if callback is not None:
444 wx.CallAfter(self._set_refresh_callback_helper)
445
446 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
447
450
451 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
452
453
454
456 if message is None:
457 self._LBL_message.Hide()
458 return
459 self._LBL_message.SetLabel(message)
460 self._LBL_message.Show()
461
462 message = property(lambda x:x, _set_message)
463
464 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
465
467 """A panel holding a generic multi-column list and action buttions."""
468
494
495
496
499
501 self._LCTRL_items.set_string_items(items = items)
502 self._LCTRL_items.set_column_widths()
503
504 if (items is None) or (len(items) == 0):
505 self._BTN_edit.Enable(False)
506 self._BTN_remove.Enable(False)
507 else:
508 self._LCTRL_items.Select(0)
509
512
515
518
519
520
522 if self.edit_callback is not None:
523 self._BTN_edit.Enable(True)
524 if self.delete_callback is not None:
525 self._BTN_remove.Enable(True)
526 if self.__select_callback is not None:
527 item = self._LCTRL_items.get_selected_item_data(only_one=True)
528 self.__select_callback(item)
529
531 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
532 self._BTN_edit.Enable(False)
533 self._BTN_remove.Enable(False)
534 if self.__select_callback is not None:
535 self.__select_callback(None)
536
547
549 if self.edit_callback is None:
550 return
551 self._on_edit_button_pressed(event)
552
566
580
596
612
628
629
630
632 return self.__new_callback
633
635 if callback is not None:
636 if not callable(callback):
637 raise ValueError('<new> callback is not a callable: %s' % callback)
638 self.__new_callback = callback
639 self._BTN_add.Enable(callback is not None)
640
641 new_callback = property(_get_new_callback, _set_new_callback)
642
644 return self.__select_callback
645
647 if callback is not None:
648 if not callable(callback):
649 raise ValueError('<select> callback is not a callable: %s' % callback)
650 self.__select_callback = callback
651
652 select_callback = property(_get_select_callback, _set_select_callback)
653
655 return self._LBL_message.GetLabel()
656
658 if msg is None:
659 self._LBL_message.Hide()
660 self._LBL_message.SetLabel(u'')
661 else:
662 self._LBL_message.SetLabel(msg)
663 self._LBL_message.Show()
664 self.Layout()
665
666 message = property(_get_message, _set_message)
667
683
684 left_extra_button = property(lambda x:x, _set_left_extra_button)
685
701
702 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
703
719
720 right_extra_button = property(lambda x:x, _set_right_extra_button)
721
722 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
723
725
727
728 try:
729 msg = kwargs['msg']
730 del kwargs['msg']
731 except KeyError:
732 msg = None
733
734 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
735
736 if msg is None:
737 self._LBL_msg.Hide()
738 else:
739 self._LBL_msg.SetLabel(msg)
740
741 self._LCTRL_left.activate_callback = self.__pick_selected
742
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
789
790
810
812 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
813 return
814
815 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
816 self._LCTRL_right.remove_item(item_idx)
817
818 if self._LCTRL_right.GetItemCount() == 0:
819 self._BTN_right2left.Enable(False)
820
821
822
824 self._BTN_left2right.Enable(True)
825
827 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
828 self._BTN_left2right.Enable(False)
829
831 self._BTN_right2left.Enable(True)
832
834 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
835 self._BTN_right2left.Enable(False)
836
839
842
844
845
846
848
849 try:
850 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
851 except KeyError:
852 kwargs['style'] = wx.LC_REPORT
853
854 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
855
856 wx.ListCtrl.__init__(self, *args, **kwargs)
857 listmixins.ListCtrlAutoWidthMixin.__init__(self)
858
859 self.__widths = None
860 self.__data = None
861 self.__activate_callback = None
862
863 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
864 self.__item_tooltip_callback = None
865 self.__tt_last_item = None
866 self.__tt_static_part = _("""Select the items you want to work on.
867
868 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.""")
869
870
871
873 """(Re)define the columns.
874
875 Note that this will (have to) delete the items.
876 """
877 self.ClearAll()
878 self.__tt_last_item = None
879 if columns is None:
880 return
881 for idx in range(len(columns)):
882 self.InsertColumn(idx, columns[idx])
883
885 """Set the column width policy.
886
887 widths = None:
888 use previous policy if any or default policy
889 widths != None:
890 use this policy and remember it for later calls
891
892 This means there is no way to *revert* to the default policy :-(
893 """
894
895 if widths is not None:
896 self.__widths = widths
897 for idx in range(len(self.__widths)):
898 self.SetColumnWidth(col = idx, width = self.__widths[idx])
899 return
900
901
902 if self.__widths is not None:
903 for idx in range(len(self.__widths)):
904 self.SetColumnWidth(col = idx, width = self.__widths[idx])
905 return
906
907
908 if self.GetItemCount() == 0:
909 width_type = wx.LIST_AUTOSIZE_USEHEADER
910 else:
911 width_type = wx.LIST_AUTOSIZE
912 for idx in range(self.GetColumnCount()):
913 self.SetColumnWidth(col = idx, width = width_type)
914
916 """All item members must be unicode()able or None."""
917
918 self.DeleteAllItems()
919 self.__data = items
920 self.__tt_last_item = None
921
922 if items is None:
923 return
924
925 for item in items:
926 try:
927 item[0]
928 if not isinstance(item, basestring):
929 is_numerically_iterable = True
930 else:
931 is_numerically_iterable = False
932 except TypeError:
933 is_numerically_iterable = False
934
935 if is_numerically_iterable:
936
937
938 col_val = unicode(item[0])
939 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
940 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
941 col_val = unicode(item[col_idx])
942 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
943 else:
944
945 col_val = unicode(item)
946 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
947
949 """<data must be a list corresponding to the item indices>"""
950 self.__data = data
951 self.__tt_last_item = None
952
959
960
962 if self.__is_single_selection:
963 return [self.GetFirstSelected()]
964 selections = []
965 idx = self.GetFirstSelected()
966 while idx != -1:
967 selections.append(idx)
968 idx = self.GetNextSelected(idx)
969 return selections
970
971 selections = property(__get_selections, set_selections)
972
973
974
976 labels = []
977 for col_idx in self.GetColumnCount():
978 col = self.GetColumn(col = col_idx)
979 labels.append(col.GetText())
980 return labels
981
983 if item_idx is not None:
984 return self.GetItem(item_idx)
985
987 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
988
990 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
991
993
994 if self.__is_single_selection or only_one:
995 return self.GetFirstSelected()
996
997 items = []
998 idx = self.GetFirstSelected()
999 while idx != -1:
1000 items.append(idx)
1001 idx = self.GetNextSelected(idx)
1002
1003 return items
1004
1006
1007 if self.__is_single_selection or only_one:
1008 return self.GetItemText(self.GetFirstSelected())
1009
1010 items = []
1011 idx = self.GetFirstSelected()
1012 while idx != -1:
1013 items.append(self.GetItemText(idx))
1014 idx = self.GetNextSelected(idx)
1015
1016 return items
1017
1019 if self.__data is None:
1020 return None
1021
1022 if item_idx is not None:
1023 return self.__data[item_idx]
1024
1025 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
1026
1028
1029 if self.__is_single_selection or only_one:
1030 if self.__data is None:
1031 return None
1032 idx = self.GetFirstSelected()
1033 if idx == -1:
1034 return None
1035 return self.__data[idx]
1036
1037 data = []
1038 if self.__data is None:
1039 return data
1040 idx = self.GetFirstSelected()
1041 while idx != -1:
1042 data.append(self.__data[idx])
1043 idx = self.GetNextSelected(idx)
1044
1045 return data
1046
1048 self.Select(idx = self.GetFirstSelected(), on = 0)
1049
1051 self.DeleteItem(item_idx)
1052 if self.__data is not None:
1053 del self.__data[item_idx]
1054 self.__tt_last_item = None
1055
1056
1057
1059 event.Skip()
1060 if self.__activate_callback is not None:
1061 self.__activate_callback(event)
1062
1064 """Update tooltip on mouse motion.
1065
1066 for s in dir(wx):
1067 if s.startswith('LIST_HITTEST'):
1068 print s, getattr(wx, s)
1069
1070 LIST_HITTEST_ABOVE 1
1071 LIST_HITTEST_BELOW 2
1072 LIST_HITTEST_NOWHERE 4
1073 LIST_HITTEST_ONITEM 672
1074 LIST_HITTEST_ONITEMICON 32
1075 LIST_HITTEST_ONITEMLABEL 128
1076 LIST_HITTEST_ONITEMRIGHT 256
1077 LIST_HITTEST_ONITEMSTATEICON 512
1078 LIST_HITTEST_TOLEFT 1024
1079 LIST_HITTEST_TORIGHT 2048
1080 """
1081 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
1082
1083
1084 if where_flag not in [
1085 wx.LIST_HITTEST_ONITEMLABEL,
1086 wx.LIST_HITTEST_ONITEMICON,
1087 wx.LIST_HITTEST_ONITEMSTATEICON,
1088 wx.LIST_HITTEST_ONITEMRIGHT,
1089 wx.LIST_HITTEST_ONITEM
1090 ]:
1091 self.__tt_last_item = None
1092 self.SetToolTipString(self.__tt_static_part)
1093 return
1094
1095
1096 if self.__tt_last_item == item_idx:
1097 return
1098
1099
1100 self.__tt_last_item = item_idx
1101
1102
1103
1104 if item_idx == wx.NOT_FOUND:
1105 self.SetToolTipString(self.__tt_static_part)
1106 return
1107
1108
1109 if self.__data is None:
1110 self.SetToolTipString(self.__tt_static_part)
1111 return
1112
1113
1114
1115
1116
1117 if (
1118 (item_idx > (len(self.__data) - 1))
1119 or
1120 (item_idx < -1)
1121 ):
1122 self.SetToolTipString(self.__tt_static_part)
1123 print "*************************************************************"
1124 print "GNUmed has detected an inconsistency with list item tooltips."
1125 print ""
1126 print "This is not a big problem and you can keep working."
1127 print ""
1128 print "However, please send us the following so we can fix GNUmed:"
1129 print ""
1130 print "item idx: %s" % item_idx
1131 print 'where flag: %s' % where_flag
1132 print 'data list length: %s' % len(self.__data)
1133 print "*************************************************************"
1134 return
1135
1136 dyna_tt = None
1137 if self.__item_tooltip_callback is not None:
1138 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
1139
1140 if dyna_tt is None:
1141 self.SetToolTipString(self.__tt_static_part)
1142 return
1143
1144 self.SetToolTipString(dyna_tt)
1145
1146
1147
1149 return self.__activate_callback
1150
1152 if callback is None:
1153 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1154 else:
1155 if not callable(callback):
1156 raise ValueError('<activate> callback is not a callable: %s' % callback)
1157 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1158 self.__activate_callback = callback
1159
1160 activate_callback = property(_get_activate_callback, _set_activate_callback)
1161
1167
1168
1169
1170
1171
1172 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1173
1174
1175
1176 if __name__ == '__main__':
1177
1178 if len(sys.argv) < 2:
1179 sys.exit()
1180
1181 if sys.argv[1] != 'test':
1182 sys.exit()
1183
1184 from Gnumed.pycommon import gmI18N
1185 gmI18N.activate_locale()
1186 gmI18N.install_domain()
1187
1188
1190 app = wx.PyWidgetTester(size = (400, 500))
1191 dlg = wx.MultiChoiceDialog (
1192 parent = None,
1193 message = 'test message',
1194 caption = 'test caption',
1195 choices = ['a', 'b', 'c', 'd', 'e']
1196 )
1197 dlg.ShowModal()
1198 sels = dlg.GetSelections()
1199 print "selected:"
1200 for sel in sels:
1201 print sel
1202
1204
1205 def edit(argument):
1206 print "editor called with:"
1207 print argument
1208
1209 def refresh(lctrl):
1210 choices = ['a', 'b', 'c']
1211 lctrl.set_string_items(choices)
1212
1213 app = wx.PyWidgetTester(size = (200, 50))
1214 chosen = get_choices_from_list (
1215
1216 caption = 'select health issues',
1217
1218
1219 columns = ['issue'],
1220 refresh_callback = refresh
1221
1222 )
1223 print "chosen:"
1224 print chosen
1225
1227 app = wx.PyWidgetTester(size = (200, 50))
1228 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1229 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1230
1231 dlg.set_string_items(['patient', 'emr', 'docs'])
1232 result = dlg.ShowModal()
1233 print result
1234 print dlg.get_picks()
1235
1236
1237
1238 test_item_picker_dlg()
1239
1240
1241
1242