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"
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
490
491
492
495
497 self._LCTRL_items.set_string_items(items = items)
498 self._LCTRL_items.set_column_widths()
499
500 if (items is None) or (len(items) == 0):
501 self._BTN_edit.Enable(False)
502 self._BTN_remove.Enable(False)
503 else:
504 self._LCTRL_items.Select(0)
505
508
511
514
515
516
518 if self.edit_callback is not None:
519 self._BTN_edit.Enable(True)
520 if self.delete_callback is not None:
521 self._BTN_remove.Enable(True)
522 if self.__select_callback is not None:
523 item = self._LCTRL_items.get_selected_item_data(only_one=True)
524 self.__select_callback(item)
525
527 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
528 self._BTN_edit.Enable(False)
529 self._BTN_remove.Enable(False)
530 if self.__select_callback is not None:
531 self.__select_callback(None)
532
543
545 if self.edit_callback is None:
546 return
547 self._on_edit_button_pressed(event)
548
562
576
577
578
580 return self.__new_callback
581
583 if callback is not None:
584 if not callable(callback):
585 raise ValueError('<new> callback is not a callable: %s' % callback)
586 self.__new_callback = callback
587 self._BTN_add.Enable(callback is not None)
588
589 new_callback = property(_get_new_callback, _set_new_callback)
590
592 return self.__select_callback
593
595 if callback is not None:
596 if not callable(callback):
597 raise ValueError('<select> callback is not a callable: %s' % callback)
598 self.__select_callback = callback
599
600 select_callback = property(_get_select_callback, _set_select_callback)
601
603 return self._LBL_message.GetLabel()
604
606 if msg is None:
607 self._LBL_message.Hide()
608 self._LBL_message.SetLabel(u'')
609 else:
610 self._LBL_message.SetLabel(msg)
611 self._LBL_message.Show()
612 self.Layout()
613
614 message = property(_get_message, _set_message)
615
616 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
617
619
621
622 try:
623 msg = kwargs['msg']
624 del kwargs['msg']
625 except KeyError:
626 msg = None
627
628 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
629
630 if msg is None:
631 self._LBL_msg.Hide()
632 else:
633 self._LBL_msg.SetLabel(msg)
634
635 self._LCTRL_left.activate_callback = self.__pick_selected
636
637
638 self._LCTRL_left.SetFocus()
639
640
641
642 - def set_columns(self, columns=None, columns_right=None):
643 self._LCTRL_left.set_columns(columns = columns)
644 if columns_right is None:
645 self._LCTRL_right.set_columns(columns = columns)
646 else:
647 if len(columns_right) < len(columns):
648 cols = columns
649 else:
650 cols = columns_right[:len(columns)]
651 self._LCTRL_right.set_columns(columns = cols)
652
660
663
668
674
677
680
681 picks = property(get_picks, lambda x:x)
682
683
684
704
706 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
707 return
708
709 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
710 self._LCTRL_right.remove_item(item_idx)
711
712 if self._LCTRL_right.GetItemCount() == 0:
713 self._BTN_right2left.Enable(False)
714
715
716
718 self._BTN_left2right.Enable(True)
719
721 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
722 self._BTN_left2right.Enable(False)
723
725 self._BTN_right2left.Enable(True)
726
728 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
729 self._BTN_right2left.Enable(False)
730
733
736
738
739
740
742
743 try:
744 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
745 except KeyError:
746 kwargs['style'] = wx.LC_REPORT
747
748 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
749
750 wx.ListCtrl.__init__(self, *args, **kwargs)
751 listmixins.ListCtrlAutoWidthMixin.__init__(self)
752
753 self.__widths = None
754 self.__data = None
755 self.__activate_callback = None
756
757 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
758 self.__item_tooltip_callback = None
759 self.__tt_last_item = None
760 self.__tt_static_part = _("""Select the items you want to work on.
761
762 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.""")
763
764
765
767 """(Re)define the columns.
768
769 Note that this will (have to) delete the items.
770 """
771 self.ClearAll()
772 self.__tt_last_item = None
773 if columns is None:
774 return
775 for idx in range(len(columns)):
776 self.InsertColumn(idx, columns[idx])
777
779 """Set the column width policy.
780
781 widths = None:
782 use previous policy if any or default policy
783 widths != None:
784 use this policy and remember it for later calls
785
786 This means there is no way to *revert* to the default policy :-(
787 """
788
789 if widths is not None:
790 self.__widths = widths
791 for idx in range(len(self.__widths)):
792 self.SetColumnWidth(col = idx, width = self.__widths[idx])
793 return
794
795
796 if self.__widths is not None:
797 for idx in range(len(self.__widths)):
798 self.SetColumnWidth(col = idx, width = self.__widths[idx])
799 return
800
801
802 if self.GetItemCount() == 0:
803 width_type = wx.LIST_AUTOSIZE_USEHEADER
804 else:
805 width_type = wx.LIST_AUTOSIZE
806 for idx in range(self.GetColumnCount()):
807 self.SetColumnWidth(col = idx, width = width_type)
808
810 """All item members must be unicode()able or None."""
811
812 self.DeleteAllItems()
813 self.__data = items
814 self.__tt_last_item = None
815
816 if items is None:
817 return
818
819 for item in items:
820 try:
821 item[0]
822 if not isinstance(item, basestring):
823 is_numerically_iterable = True
824 else:
825 is_numerically_iterable = False
826 except TypeError:
827 is_numerically_iterable = False
828
829 if is_numerically_iterable:
830
831
832 col_val = unicode(item[0])
833 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
834 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
835 col_val = unicode(item[col_idx])
836 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
837 else:
838
839 col_val = unicode(item)
840 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
841
843 """<data must be a list corresponding to the item indices>"""
844 self.__data = data
845 self.__tt_last_item = None
846
853
854
856 if self.__is_single_selection:
857 return [self.GetFirstSelected()]
858 selections = []
859 idx = self.GetFirstSelected()
860 while idx != -1:
861 selections.append(idx)
862 idx = self.GetNextSelected(idx)
863 return selections
864
865 selections = property(__get_selections, set_selections)
866
867
868
870 labels = []
871 for col_idx in self.GetColumnCount():
872 col = self.GetColumn(col = col_idx)
873 labels.append(col.GetText())
874 return labels
875
877 if item_idx is not None:
878 return self.GetItem(item_idx)
879
881 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
882
884 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
885
887
888 if self.__is_single_selection or only_one:
889 return self.GetFirstSelected()
890
891 items = []
892 idx = self.GetFirstSelected()
893 while idx != -1:
894 items.append(idx)
895 idx = self.GetNextSelected(idx)
896
897 return items
898
900
901 if self.__is_single_selection or only_one:
902 return self.GetItemText(self.GetFirstSelected())
903
904 items = []
905 idx = self.GetFirstSelected()
906 while idx != -1:
907 items.append(self.GetItemText(idx))
908 idx = self.GetNextSelected(idx)
909
910 return items
911
913 if self.__data is None:
914 return None
915
916 if item_idx is not None:
917 return self.__data[item_idx]
918
919 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
920
922
923 if self.__is_single_selection or only_one:
924 if self.__data is None:
925 return None
926 idx = self.GetFirstSelected()
927 if idx == -1:
928 return None
929 return self.__data[idx]
930
931 data = []
932 if self.__data is None:
933 return data
934 idx = self.GetFirstSelected()
935 while idx != -1:
936 data.append(self.__data[idx])
937 idx = self.GetNextSelected(idx)
938
939 return data
940
942 self.Select(idx = self.GetFirstSelected(), on = 0)
943
945 self.DeleteItem(item_idx)
946 if self.__data is not None:
947 del self.__data[item_idx]
948 self.__tt_last_item = None
949
950
951
953 event.Skip()
954 if self.__activate_callback is not None:
955 self.__activate_callback(event)
956
958 """Update tooltip on mouse motion.
959
960 for s in dir(wx):
961 if s.startswith('LIST_HITTEST'):
962 print s, getattr(wx, s)
963
964 LIST_HITTEST_ABOVE 1
965 LIST_HITTEST_BELOW 2
966 LIST_HITTEST_NOWHERE 4
967 LIST_HITTEST_ONITEM 672
968 LIST_HITTEST_ONITEMICON 32
969 LIST_HITTEST_ONITEMLABEL 128
970 LIST_HITTEST_ONITEMRIGHT 256
971 LIST_HITTEST_ONITEMSTATEICON 512
972 LIST_HITTEST_TOLEFT 1024
973 LIST_HITTEST_TORIGHT 2048
974 """
975 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
976
977
978 if where_flag not in [
979 wx.LIST_HITTEST_ONITEMLABEL,
980 wx.LIST_HITTEST_ONITEMICON,
981 wx.LIST_HITTEST_ONITEMSTATEICON,
982 wx.LIST_HITTEST_ONITEMRIGHT,
983 wx.LIST_HITTEST_ONITEM
984 ]:
985 self.__tt_last_item = None
986 self.SetToolTipString(self.__tt_static_part)
987 return
988
989
990 if self.__tt_last_item == item_idx:
991 return
992
993
994 self.__tt_last_item = item_idx
995
996
997
998 if item_idx == wx.NOT_FOUND:
999 self.SetToolTipString(self.__tt_static_part)
1000 return
1001
1002
1003 if self.__data is None:
1004 self.SetToolTipString(self.__tt_static_part)
1005 return
1006
1007
1008
1009
1010
1011 if (
1012 (item_idx > (len(self.__data) - 1))
1013 or
1014 (item_idx < -1)
1015 ):
1016 self.SetToolTipString(self.__tt_static_part)
1017 print "*************************************************************"
1018 print "GNUmed has detected an inconsistency with list item tooltips."
1019 print ""
1020 print "This is not a big problem and you can keep working."
1021 print ""
1022 print "However, please send us the following so we can fix GNUmed:"
1023 print ""
1024 print "item idx: %s" % item_idx
1025 print 'where flag: %s' % where_flag
1026 print 'data list length: %s' % len(self.__data)
1027 print "*************************************************************"
1028 return
1029
1030 dyna_tt = None
1031 if self.__item_tooltip_callback is not None:
1032 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
1033
1034 if dyna_tt is None:
1035 self.SetToolTipString(self.__tt_static_part)
1036 return
1037
1038 self.SetToolTipString(dyna_tt)
1039
1040
1041
1043 return self.__activate_callback
1044
1046 if callback is None:
1047 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1048 else:
1049 if not callable(callback):
1050 raise ValueError('<activate> callback is not a callable: %s' % callback)
1051 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1052 self.__activate_callback = callback
1053
1054 activate_callback = property(_get_activate_callback, _set_activate_callback)
1055
1061
1062
1063
1064
1065
1066 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1067
1068
1069
1070 if __name__ == '__main__':
1071
1072 if len(sys.argv) < 2:
1073 sys.exit()
1074
1075 if sys.argv[1] != 'test':
1076 sys.exit()
1077
1078 from Gnumed.pycommon import gmI18N
1079 gmI18N.activate_locale()
1080 gmI18N.install_domain()
1081
1082
1084 app = wx.PyWidgetTester(size = (400, 500))
1085 dlg = wx.MultiChoiceDialog (
1086 parent = None,
1087 message = 'test message',
1088 caption = 'test caption',
1089 choices = ['a', 'b', 'c', 'd', 'e']
1090 )
1091 dlg.ShowModal()
1092 sels = dlg.GetSelections()
1093 print "selected:"
1094 for sel in sels:
1095 print sel
1096
1098
1099 def edit(argument):
1100 print "editor called with:"
1101 print argument
1102
1103 def refresh(lctrl):
1104 choices = ['a', 'b', 'c']
1105 lctrl.set_string_items(choices)
1106
1107 app = wx.PyWidgetTester(size = (200, 50))
1108 chosen = get_choices_from_list (
1109
1110 caption = 'select health issues',
1111
1112
1113 columns = ['issue'],
1114 refresh_callback = refresh
1115
1116 )
1117 print "chosen:"
1118 print chosen
1119
1121 app = wx.PyWidgetTester(size = (200, 50))
1122 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1123 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1124
1125 dlg.set_string_items(['patient', 'emr', 'docs'])
1126 result = dlg.ShowModal()
1127 print result
1128 print dlg.get_picks()
1129
1130
1131
1132 test_item_picker_dlg()
1133
1134
1135
1136