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
682
702
704 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
705 return
706
707 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
708 self._LCTRL_right.remove_item(item_idx)
709
710 if self._LCTRL_right.GetItemCount() == 0:
711 self._BTN_right2left.Enable(False)
712
713
714
716 self._BTN_left2right.Enable(True)
717
719 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
720 self._BTN_left2right.Enable(False)
721
723 self._BTN_right2left.Enable(True)
724
726 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
727 self._BTN_right2left.Enable(False)
728
731
734
736
737
738
740
741 try:
742 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
743 except KeyError:
744 kwargs['style'] = wx.LC_REPORT
745
746 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
747
748 wx.ListCtrl.__init__(self, *args, **kwargs)
749 listmixins.ListCtrlAutoWidthMixin.__init__(self)
750
751 self.__widths = None
752 self.__data = None
753 self.__activate_callback = None
754
755 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
756 self.__item_tooltip_callback = None
757 self.__tt_last_item = None
758 self.__tt_static_part = _("""Select the items you want to work on.
759
760 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.""")
761
762
763
765 """(Re)define the columns.
766
767 Note that this will (have to) delete the items.
768 """
769 self.ClearAll()
770 self.__tt_last_item = None
771 if columns is None:
772 return
773 for idx in range(len(columns)):
774 self.InsertColumn(idx, columns[idx])
775
777 """Set the column width policy.
778
779 widths = None:
780 use previous policy if any or default policy
781 widths != None:
782 use this policy and remember it for later calls
783
784 This means there is no way to *revert* to the default policy :-(
785 """
786
787 if widths is not None:
788 self.__widths = widths
789 for idx in range(len(self.__widths)):
790 self.SetColumnWidth(col = idx, width = self.__widths[idx])
791 return
792
793
794 if self.__widths is not None:
795 for idx in range(len(self.__widths)):
796 self.SetColumnWidth(col = idx, width = self.__widths[idx])
797 return
798
799
800 if self.GetItemCount() == 0:
801 width_type = wx.LIST_AUTOSIZE_USEHEADER
802 else:
803 width_type = wx.LIST_AUTOSIZE
804 for idx in range(self.GetColumnCount()):
805 self.SetColumnWidth(col = idx, width = width_type)
806
808 """All item members must be unicode()able or None."""
809
810 self.DeleteAllItems()
811 self.__data = items
812 self.__tt_last_item = None
813
814 if items is None:
815 return
816
817 for item in items:
818 try:
819 item[0]
820 if not isinstance(item, basestring):
821 is_numerically_iterable = True
822 else:
823 is_numerically_iterable = False
824 except TypeError:
825 is_numerically_iterable = False
826
827 if is_numerically_iterable:
828
829
830 col_val = unicode(item[0])
831 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
832 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
833 col_val = unicode(item[col_idx])
834 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
835 else:
836
837 col_val = unicode(item)
838 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
839
841 """<data must be a list corresponding to the item indices>"""
842 self.__data = data
843 self.__tt_last_item = None
844
851
852
854 if self.__is_single_selection:
855 return [self.GetFirstSelected()]
856 selections = []
857 idx = self.GetFirstSelected()
858 while idx != -1:
859 selections.append(idx)
860 idx = self.GetNextSelected(idx)
861 return selections
862
863 selections = property(__get_selections, set_selections)
864
865
866
868 labels = []
869 for col_idx in self.GetColumnCount():
870 col = self.GetColumn(col = col_idx)
871 labels.append(col.GetText())
872 return labels
873
875 if item_idx is not None:
876 return self.GetItem(item_idx)
877
879 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
880
882 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
883
885
886 if self.__is_single_selection or only_one:
887 return self.GetFirstSelected()
888
889 items = []
890 idx = self.GetFirstSelected()
891 while idx != -1:
892 items.append(idx)
893 idx = self.GetNextSelected(idx)
894
895 return items
896
898
899 if self.__is_single_selection or only_one:
900 return self.GetItemText(self.GetFirstSelected())
901
902 items = []
903 idx = self.GetFirstSelected()
904 while idx != -1:
905 items.append(self.GetItemText(idx))
906 idx = self.GetNextSelected(idx)
907
908 return items
909
911 if self.__data is None:
912 return None
913
914 if item_idx is not None:
915 return self.__data[item_idx]
916
917 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
918
920
921 if self.__is_single_selection or only_one:
922 if self.__data is None:
923 return None
924 idx = self.GetFirstSelected()
925 if idx == -1:
926 return None
927 return self.__data[idx]
928
929 data = []
930 if self.__data is None:
931 return data
932 idx = self.GetFirstSelected()
933 while idx != -1:
934 data.append(self.__data[idx])
935 idx = self.GetNextSelected(idx)
936
937 return data
938
940 self.Select(idx = self.GetFirstSelected(), on = 0)
941
943 self.DeleteItem(item_idx)
944 if self.__data is not None:
945 del self.__data[item_idx]
946 self.__tt_last_item = None
947
948
949
951 event.Skip()
952 if self.__activate_callback is not None:
953 self.__activate_callback(event)
954
956 """Update tooltip on mouse motion.
957
958 for s in dir(wx):
959 if s.startswith('LIST_HITTEST'):
960 print s, getattr(wx, s)
961
962 LIST_HITTEST_ABOVE 1
963 LIST_HITTEST_BELOW 2
964 LIST_HITTEST_NOWHERE 4
965 LIST_HITTEST_ONITEM 672
966 LIST_HITTEST_ONITEMICON 32
967 LIST_HITTEST_ONITEMLABEL 128
968 LIST_HITTEST_ONITEMRIGHT 256
969 LIST_HITTEST_ONITEMSTATEICON 512
970 LIST_HITTEST_TOLEFT 1024
971 LIST_HITTEST_TORIGHT 2048
972 """
973 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
974
975
976 if where_flag not in [
977 wx.LIST_HITTEST_ONITEMLABEL,
978 wx.LIST_HITTEST_ONITEMICON,
979 wx.LIST_HITTEST_ONITEMSTATEICON,
980 wx.LIST_HITTEST_ONITEMRIGHT,
981 wx.LIST_HITTEST_ONITEM
982 ]:
983 self.__tt_last_item = None
984 self.SetToolTipString(self.__tt_static_part)
985 return
986
987
988 if self.__tt_last_item == item_idx:
989 return
990
991
992 self.__tt_last_item = item_idx
993
994
995
996 if item_idx == wx.NOT_FOUND:
997 self.SetToolTipString(self.__tt_static_part)
998 return
999
1000
1001 if self.__data is None:
1002 self.SetToolTipString(self.__tt_static_part)
1003 return
1004
1005
1006
1007
1008
1009 if (
1010 (item_idx > (len(self.__data) - 1))
1011 or
1012 (item_idx < -1)
1013 ):
1014 self.SetToolTipString(self.__tt_static_part)
1015 print "*************************************************************"
1016 print "GNUmed has detected an inconsistency with list item tooltips."
1017 print ""
1018 print "This is not a big problem and you can keep working."
1019 print ""
1020 print "However, please send us the following so we can fix GNUmed:"
1021 print ""
1022 print "item idx: %s" % item_idx
1023 print 'where flag: %s' % where_flag
1024 print 'data list length: %s' % len(self.__data)
1025 print "*************************************************************"
1026 return
1027
1028 dyna_tt = None
1029 if self.__item_tooltip_callback is not None:
1030 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
1031
1032 if dyna_tt is None:
1033 self.SetToolTipString(self.__tt_static_part)
1034 return
1035
1036 self.SetToolTipString(dyna_tt)
1037
1038
1039
1041 return self.__activate_callback
1042
1044 if callback is None:
1045 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1046 else:
1047 if not callable(callback):
1048 raise ValueError('<activate> callback is not a callable: %s' % callback)
1049 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1050 self.__activate_callback = callback
1051
1052 activate_callback = property(_get_activate_callback, _set_activate_callback)
1053
1059
1060 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1061
1062
1063
1064 if __name__ == '__main__':
1065
1066 if len(sys.argv) < 2:
1067 sys.exit()
1068
1069 if sys.argv[1] != 'test':
1070 sys.exit()
1071
1072 from Gnumed.pycommon import gmI18N
1073 gmI18N.activate_locale()
1074 gmI18N.install_domain()
1075
1076
1078 app = wx.PyWidgetTester(size = (400, 500))
1079 dlg = wx.MultiChoiceDialog (
1080 parent = None,
1081 message = 'test message',
1082 caption = 'test caption',
1083 choices = ['a', 'b', 'c', 'd', 'e']
1084 )
1085 dlg.ShowModal()
1086 sels = dlg.GetSelections()
1087 print "selected:"
1088 for sel in sels:
1089 print sel
1090
1092
1093 def edit(argument):
1094 print "editor called with:"
1095 print argument
1096
1097 def refresh(lctrl):
1098 choices = ['a', 'b', 'c']
1099 lctrl.set_string_items(choices)
1100
1101 app = wx.PyWidgetTester(size = (200, 50))
1102 chosen = get_choices_from_list (
1103
1104 caption = 'select health issues',
1105
1106
1107 columns = ['issue'],
1108 refresh_callback = refresh
1109
1110 )
1111 print "chosen:"
1112 print chosen
1113
1115 app = wx.PyWidgetTester(size = (200, 50))
1116 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1117 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1118
1119 dlg.set_string_items(['patient', 'emr', 'docs'])
1120 result = dlg.ShowModal()
1121 print result
1122 print dlg.get_picks()
1123
1124
1125
1126 test_item_picker_dlg()
1127
1128
1129
1130