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
159 self._LCTRL_items.set_selections(selections = selections)
160 if selections is None:
161 return
162 if len(selections) == 0:
163 return
164 if self.ignore_OK_button:
165 return
166 self._BTN_ok.Enable(True)
167 self._BTN_ok.SetDefault()
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
207
221
238
251
264
277
278
279
293
294 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
295
311
312 left_extra_button = property(lambda x:x, _set_left_extra_button)
313
329
330 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
331
347
348 right_extra_button = property(lambda x:x, _set_right_extra_button)
349
351 return self.__new_callback
352
354 if callback is not None:
355 if self.refresh_callback is None:
356 raise ValueError('refresh callback must be set before new callback can be set')
357 if not callable(callback):
358 raise ValueError('<new> callback is not a callable: %s' % callback)
359 self.__new_callback = callback
360
361 if callback is None:
362 self._BTN_new.Enable(False)
363 self._BTN_new.Hide()
364 else:
365 self._BTN_new.Enable(True)
366 self._BTN_new.Show()
367
368 new_callback = property(_get_new_callback, _set_new_callback)
369
371 return self.__edit_callback
372
374 if callback is not None:
375 if not callable(callback):
376 raise ValueError('<edit> callback is not a callable: %s' % callback)
377 self.__edit_callback = callback
378
379 if callback is None:
380 self._BTN_edit.Enable(False)
381 self._BTN_edit.Hide()
382 else:
383 self._BTN_edit.Enable(True)
384 self._BTN_edit.Show()
385
386 edit_callback = property(_get_edit_callback, _set_edit_callback)
387
389 return self.__delete_callback
390
392 if callback is not None:
393 if self.refresh_callback is None:
394 raise ValueError('refresh callback must be set before delete callback can be set')
395 if not callable(callback):
396 raise ValueError('<delete> callback is not a callable: %s' % callback)
397 self.__delete_callback = callback
398
399 if callback is None:
400 self._BTN_delete.Enable(False)
401 self._BTN_delete.Hide()
402 else:
403 self._BTN_delete.Enable(True)
404 self._BTN_delete.Show()
405
406 delete_callback = property(_get_delete_callback, _set_delete_callback)
407
409 return self.__refresh_callback
410
418
420 if callback is not None:
421 if not callable(callback):
422 raise ValueError('<refresh> callback is not a callable: %s' % callback)
423 self.__refresh_callback = callback
424 if callback is not None:
425 wx.CallAfter(self._set_refresh_callback_helper)
426
427 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
428
431
432 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
433
434
435
437 if message is None:
438 self._LBL_message.Hide()
439 return
440 self._LBL_message.SetLabel(message)
441 self._LBL_message.Show()
442
443 message = property(lambda x:x, _set_message)
444
445 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
446
448 """A panel holding a generic multi-column list and action buttions."""
449
469
470
471
474
476 self._LCTRL_items.set_string_items(items = items)
477 self._LCTRL_items.set_column_widths()
478
479 if (items is None) or (len(items) == 0):
480 self._BTN_edit.Enable(False)
481 self._BTN_remove.Enable(False)
482 else:
483 self._LCTRL_items.Select(0)
484
487
490
493
494
495
497 if self.edit_callback is not None:
498 self._BTN_edit.Enable(True)
499 if self.delete_callback is not None:
500 self._BTN_remove.Enable(True)
501
503 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
504 self._BTN_edit.Enable(False)
505 self._BTN_remove.Enable(False)
506
517
531
545
546
547
549 return self.__new_callback
550
552 self.__new_callback = callback
553 self._BTN_add.Enable(callback is not None)
554
555 new_callback = property(_get_new_callback, _set_new_callback)
556
557 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
558
560
562
563 try:
564 msg = kwargs['msg']
565 del kwargs['msg']
566 except KeyError:
567 msg = None
568
569 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
570
571 if msg is None:
572 self._LBL_msg.Hide()
573 else:
574 self._LBL_msg.SetLabel(msg)
575
576 self._LCTRL_left.activate_callback = self.__pick_selected
577
578
579 self._LCTRL_left.SetFocus()
580
581
582
583 - def set_columns(self, columns=None, columns_right=None):
584 self._LCTRL_left.set_columns(columns = columns)
585 if columns_right is None:
586 self._LCTRL_right.set_columns(columns = columns)
587 else:
588 if len(columns_right) < len(columns):
589 cols = columns
590 else:
591 cols = columns_right[:len(columns)]
592 self._LCTRL_right.set_columns(columns = cols)
593
601
604
609
615
618
621
622
623
643
645 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
646 return
647
648 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
649 self._LCTRL_right.remove_item(item_idx)
650
651 if self._LCTRL_right.GetItemCount() == 0:
652 self._BTN_right2left.Enable(False)
653
654
655
657 self._BTN_left2right.Enable(True)
658
660 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
661 self._BTN_left2right.Enable(False)
662
664 self._BTN_right2left.Enable(True)
665
667 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
668 self._BTN_right2left.Enable(False)
669
672
675
677
678
679
681
682 try:
683 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
684 except KeyError:
685 kwargs['style'] = wx.LC_REPORT
686
687 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
688
689 wx.ListCtrl.__init__(self, *args, **kwargs)
690 listmixins.ListCtrlAutoWidthMixin.__init__(self)
691
692 self.__widths = None
693 self.__data = None
694 self.__activate_callback = None
695
696 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
697 self.__item_tooltip_callback = None
698 self.__tt_last_item = None
699 self.__tt_static_part = _("""Select the items you want to work on.
700
701 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.""")
702
703
704
706 """(Re)define the columns.
707
708 Note that this will (have to) delete the items.
709 """
710 self.ClearAll()
711 self.__tt_last_item = None
712 if columns is None:
713 return
714 for idx in range(len(columns)):
715 self.InsertColumn(idx, columns[idx])
716
718 """Set the column width policy.
719
720 widths = None:
721 use previous policy if any or default policy
722 widths != None:
723 use this policy and remember it for later calls
724
725 This means there is no way to *revert* to the default policy :-(
726 """
727
728 if widths is not None:
729 self.__widths = widths
730 for idx in range(len(self.__widths)):
731 self.SetColumnWidth(col = idx, width = self.__widths[idx])
732 return
733
734
735 if self.__widths is not None:
736 for idx in range(len(self.__widths)):
737 self.SetColumnWidth(col = idx, width = self.__widths[idx])
738 return
739
740
741 if self.GetItemCount() == 0:
742 width_type = wx.LIST_AUTOSIZE_USEHEADER
743 else:
744 width_type = wx.LIST_AUTOSIZE
745 for idx in range(self.GetColumnCount()):
746 self.SetColumnWidth(col = idx, width = width_type)
747
749 """All item members must be unicode()able or None."""
750
751 self.DeleteAllItems()
752 self.__data = items
753 self.__tt_last_item = None
754
755 if items is None:
756 return
757
758 for item in items:
759 try:
760 item[0]
761 if not isinstance(item, basestring):
762 is_numerically_iterable = True
763 else:
764 is_numerically_iterable = False
765 except TypeError:
766 is_numerically_iterable = False
767
768 if is_numerically_iterable:
769
770
771 col_val = unicode(item[0])
772 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
773 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
774 col_val = unicode(item[col_idx])
775 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
776 else:
777
778 col_val = unicode(item)
779 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
780
782 """<data must be a list corresponding to the item indices>"""
783 self.__data = data
784 self.__tt_last_item = None
785
787 self.Select(0, on = 0)
788 for idx in selections:
789 self.Select(idx = idx, on = 1)
790
791
792
793
795 labels = []
796 for col_idx in self.GetColumnCount():
797 col = self.GetColumn(col = col_idx)
798 labels.append(col.GetText())
799 return labels
800
802 if item_idx is not None:
803 return self.GetItem(item_idx)
804
806 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
807
809 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
810
812
813 if self.__is_single_selection or only_one:
814 return self.GetFirstSelected()
815
816 items = []
817 idx = self.GetFirstSelected()
818 while idx != -1:
819 items.append(idx)
820 idx = self.GetNextSelected(idx)
821
822 return items
823
825
826 if self.__is_single_selection or only_one:
827 return self.GetItemText(self.GetFirstSelected())
828
829 items = []
830 idx = self.GetFirstSelected()
831 while idx != -1:
832 items.append(self.GetItemText(idx))
833 idx = self.GetNextSelected(idx)
834
835 return items
836
838 if self.__data is None:
839 return None
840
841 if item_idx is not None:
842 return self.__data[item_idx]
843
844 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
845
847
848 if self.__is_single_selection or only_one:
849 if self.__data is None:
850 return None
851 idx = self.GetFirstSelected()
852 if idx == -1:
853 return None
854 return self.__data[idx]
855
856 data = []
857 if self.__data is None:
858 return data
859 idx = self.GetFirstSelected()
860 while idx != -1:
861 data.append(self.__data[idx])
862 idx = self.GetNextSelected(idx)
863
864 return data
865
867 self.Select(idx = self.GetFirstSelected(), on = 0)
868
870 self.DeleteItem(item_idx)
871 if self.__data is not None:
872 del self.__data[item_idx]
873 self.__tt_last_item = None
874
875
876
878 event.Skip()
879 if self.__activate_callback is not None:
880 self.__activate_callback(event)
881
883 """Update tooltip on mouse motion.
884
885 for s in dir(wx):
886 if s.startswith('LIST_HITTEST'):
887 print s, getattr(wx, s)
888
889 LIST_HITTEST_ABOVE 1
890 LIST_HITTEST_BELOW 2
891 LIST_HITTEST_NOWHERE 4
892 LIST_HITTEST_ONITEM 672
893 LIST_HITTEST_ONITEMICON 32
894 LIST_HITTEST_ONITEMLABEL 128
895 LIST_HITTEST_ONITEMRIGHT 256
896 LIST_HITTEST_ONITEMSTATEICON 512
897 LIST_HITTEST_TOLEFT 1024
898 LIST_HITTEST_TORIGHT 2048
899 """
900 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
901
902
903 if where_flag not in [
904 wx.LIST_HITTEST_ONITEMLABEL,
905 wx.LIST_HITTEST_ONITEMICON,
906 wx.LIST_HITTEST_ONITEMSTATEICON,
907 wx.LIST_HITTEST_ONITEMRIGHT,
908 wx.LIST_HITTEST_ONITEM
909 ]:
910 self.__tt_last_item = None
911 self.SetToolTipString(self.__tt_static_part)
912 return
913
914
915 if self.__tt_last_item == item_idx:
916 return
917
918
919 self.__tt_last_item = item_idx
920
921
922
923 if item_idx == -1:
924 self.SetToolTipString(self.__tt_static_part)
925 return
926
927
928 if self.__data is None:
929 self.SetToolTipString(self.__tt_static_part)
930 return
931
932
933
934
935
936 if (
937 (item_idx > len(self.__data))
938 or
939 (item_idx < -1)
940 ):
941 self.SetToolTipString(self.__tt_static_part)
942 _log.error('item idx: %s', item_idx)
943 _log.error('where flag: %s', where_flag)
944 _log.error('data list length: %s', len(self.__data))
945 for data in self.__data:
946 _log.debug(data)
947 print "*************************************************************"
948 print "GNUmed has detected an inconsistency with list item tooltips."
949 print ""
950 print "This is not a big problem and you can keep working."
951 print ""
952 print "However, please send us the log file so we can fix GNUmed."
953 print "*************************************************************"
954 return
955
956 dyna_tt = None
957 if self.__item_tooltip_callback is not None:
958 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
959
960 if dyna_tt is None:
961 self.SetToolTipString(self.__tt_static_part)
962 return
963
964 self.SetToolTipString(dyna_tt)
965
966
967
969 return self.__activate_callback
970
972 if callback is None:
973 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
974 else:
975 if not callable(callback):
976 raise ValueError('<activate> callback is not a callable: %s' % callback)
977 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
978 self.__activate_callback = callback
979
980 activate_callback = property(_get_activate_callback, _set_activate_callback)
981
987
988 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
989
990
991
992 if __name__ == '__main__':
993
994 if len(sys.argv) < 2:
995 sys.exit()
996
997 if sys.argv[1] != 'test':
998 sys.exit()
999
1000 from Gnumed.pycommon import gmI18N
1001 gmI18N.activate_locale()
1002 gmI18N.install_domain()
1003
1004
1006 app = wx.PyWidgetTester(size = (400, 500))
1007 dlg = wx.MultiChoiceDialog (
1008 parent = None,
1009 message = 'test message',
1010 caption = 'test caption',
1011 choices = ['a', 'b', 'c', 'd', 'e']
1012 )
1013 dlg.ShowModal()
1014 sels = dlg.GetSelections()
1015 print "selected:"
1016 for sel in sels:
1017 print sel
1018
1020
1021 def edit(argument):
1022 print "editor called with:"
1023 print argument
1024
1025 def refresh(lctrl):
1026 choices = ['a', 'b', 'c']
1027 lctrl.set_string_items(choices)
1028
1029 app = wx.PyWidgetTester(size = (200, 50))
1030 chosen = get_choices_from_list (
1031
1032 caption = 'select health issues',
1033
1034
1035 columns = ['issue'],
1036 refresh_callback = refresh
1037
1038 )
1039 print "chosen:"
1040 print chosen
1041
1043 app = wx.PyWidgetTester(size = (200, 50))
1044 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1045 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1046
1047 dlg.set_string_items(['patient', 'emr', 'docs'])
1048 result = dlg.ShowModal()
1049 print result
1050 print dlg.get_picks()
1051
1052
1053
1054 test_item_picker_dlg()
1055
1056
1057
1058