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 from Gnumed.pycommon import gmTools, gmDispatcher
30 from Gnumed.wxpython import gmGuiHelpers
31 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg, wxgGenericListManagerPnl
32
33
34
35
36 -def get_choices_from_list (
37 parent=None,
38 msg=None,
39 caption=None,
40 choices=None,
41 selections=None,
42 columns=None,
43 data=None,
44 edit_callback=None,
45 new_callback=None,
46 delete_callback=None,
47 refresh_callback=None,
48 single_selection=False,
49 can_return_empty=False,
50 ignore_OK_button=False,
51 left_extra_button=None,
52 middle_extra_button=None,
53 right_extra_button=None,
54 list_tooltip_callback=None):
119
121 """A dialog holding a list and a few buttons to act on the items."""
122
123
124
148
151
154
159
162
165
168
169
170
172 if not self.__ignore_OK_button:
173 self._BTN_ok.SetDefault()
174 self._BTN_ok.Enable(True)
175
176 if self.edit_callback is not None:
177 self._BTN_edit.Enable(True)
178
179 if self.delete_callback is not None:
180 self._BTN_delete.Enable(True)
181
183 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
184 if not self.can_return_empty:
185 self._BTN_cancel.SetDefault()
186 self._BTN_ok.Enable(False)
187 self._BTN_edit.Enable(False)
188 self._BTN_delete.Enable(False)
189
201
215
232
245
258
271
272
273
282
283 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
284
300
301 left_extra_button = property(lambda x:x, _set_left_extra_button)
302
318
319 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
320
336
337 right_extra_button = property(lambda x:x, _set_right_extra_button)
338
340 return self.__new_callback
341
343 if callback is not None:
344 if self.refresh_callback is None:
345 raise ValueError('refresh callback must be set before new callback can be set')
346 if not callable(callback):
347 raise ValueError('<new> callback is not a callable: %s' % callback)
348 self.__new_callback = callback
349
350 if callback is None:
351 self._BTN_new.Enable(False)
352 self._BTN_new.Hide()
353 else:
354 self._BTN_new.Enable(True)
355 self._BTN_new.Show()
356
357 new_callback = property(_get_new_callback, _set_new_callback)
358
360 return self.__edit_callback
361
363 if callback is not None:
364 if not callable(callback):
365 raise ValueError('<edit> callback is not a callable: %s' % callback)
366 self.__edit_callback = callback
367
368 if callback is None:
369 self._BTN_edit.Enable(False)
370 self._BTN_edit.Hide()
371 else:
372 self._BTN_edit.Enable(True)
373 self._BTN_edit.Show()
374
375 edit_callback = property(_get_edit_callback, _set_edit_callback)
376
378 return self.__delete_callback
379
381 if callback is not None:
382 if self.refresh_callback is None:
383 raise ValueError('refresh callback must be set before delete callback can be set')
384 if not callable(callback):
385 raise ValueError('<delete> callback is not a callable: %s' % callback)
386 self.__delete_callback = callback
387
388 if callback is None:
389 self._BTN_delete.Enable(False)
390 self._BTN_delete.Hide()
391 else:
392 self._BTN_delete.Enable(True)
393 self._BTN_delete.Show()
394
395 delete_callback = property(_get_delete_callback, _set_delete_callback)
396
398 return self.__refresh_callback
399
407
409 if callback is not None:
410 if not callable(callback):
411 raise ValueError('<refresh> callback is not a callable: %s' % callback)
412 self.__refresh_callback = callback
413 if callback is not None:
414 wx.CallAfter(self._set_refresh_callback_helper)
415
416 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
417
420
421 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
422
423
424
426 if message is None:
427 self._LBL_message.Hide()
428 return
429 self._LBL_message.SetLabel(message)
430 self._LBL_message.Show()
431
432 message = property(lambda x:x, _set_message)
433
435 """A panel holding a generic multi-column list and action buttions."""
436
456
457
458
461
463 self._LCTRL_items.set_string_items(items = items)
464 self._LCTRL_items.set_column_widths()
465
466 if (items is None) or (len(items) == 0):
467 self._BTN_edit.Enable(False)
468 self._BTN_remove.Enable(False)
469 else:
470 self._LCTRL_items.Select(0)
471
474
477
480
481
482
484 if self.edit_callback is not None:
485 self._BTN_edit.Enable(True)
486 if self.delete_callback is not None:
487 self._BTN_remove.Enable(True)
488
490 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
491 self._BTN_edit.Enable(False)
492 self._BTN_remove.Enable(False)
493
504
518
532
533
534
536 return self.__new_callback
537
539 self.__new_callback = callback
540 self._BTN_add.Enable(callback is not None)
541
542 new_callback = property(_get_new_callback, _set_new_callback)
543
544 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
545
547
549
550 try:
551 msg = kwargs['msg']
552 del kwargs['msg']
553 except KeyError:
554 msg = None
555
556 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
557
558 if msg is None:
559 self._LBL_msg.Hide()
560 else:
561 self._LBL_msg.SetLabel(msg)
562
563 self._LCTRL_left.activate_callback = self.__pick_selected
564
565
566 self._LCTRL_left.SetFocus()
567
568
569
570 - def set_columns(self, columns=None, columns_right=None):
571 self._LCTRL_left.set_columns(columns = columns)
572 if columns_right is None:
573 self._LCTRL_right.set_columns(columns = columns)
574 else:
575 if len(columns_right) < len(columns):
576 cols = columns
577 else:
578 cols = columns_right[:len(columns)]
579 self._LCTRL_right.set_columns(columns = cols)
580
588
591
596
602
605
608
609
610
630
632 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
633 return
634
635 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
636 self._LCTRL_right.remove_item(item_idx)
637
638 if self._LCTRL_right.GetItemCount() == 0:
639 self._BTN_right2left.Enable(False)
640
641
642
644 self._BTN_left2right.Enable(True)
645
647 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
648 self._BTN_left2right.Enable(False)
649
651 self._BTN_right2left.Enable(True)
652
654 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
655 self._BTN_right2left.Enable(False)
656
659
662
664
665
666
668
669 try:
670 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
671 except KeyError:
672 kwargs['style'] = wx.LC_REPORT
673
674 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
675
676 wx.ListCtrl.__init__(self, *args, **kwargs)
677 listmixins.ListCtrlAutoWidthMixin.__init__(self)
678
679 self.__widths = None
680 self.__data = None
681 self.__activate_callback = None
682
683 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
684 self.__item_tooltip_callback = None
685 self.__tt_last_item = None
686 self.__tt_static_part = _("""Select the items you want to work on.
687
688 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.""")
689
690
691
693 """(Re)define the columns.
694
695 Note that this will (have to) delete the items.
696 """
697 self.ClearAll()
698 self.__tt_last_item = None
699 if columns is None:
700 return
701 for idx in range(len(columns)):
702 self.InsertColumn(idx, columns[idx])
703
705 """Set the column width policy.
706
707 widths = None:
708 use previous policy if any or default policy
709 widths != None:
710 use this policy and remember it for later calls
711
712 This means there is no way to *revert* to the default policy :-(
713 """
714
715 if widths is not None:
716 self.__widths = widths
717 for idx in range(len(self.__widths)):
718 self.SetColumnWidth(col = idx, width = self.__widths[idx])
719 return
720
721
722 if self.__widths is not None:
723 for idx in range(len(self.__widths)):
724 self.SetColumnWidth(col = idx, width = self.__widths[idx])
725 return
726
727
728 if self.GetItemCount() == 0:
729 width_type = wx.LIST_AUTOSIZE_USEHEADER
730 else:
731 width_type = wx.LIST_AUTOSIZE
732 for idx in range(self.GetColumnCount()):
733 self.SetColumnWidth(col = idx, width = width_type)
734
736 """All item members must be unicode()able or None."""
737
738 self.DeleteAllItems()
739 self.__data = items
740 self.__tt_last_item = None
741
742 if items is None:
743 return
744
745 for item in items:
746 try:
747 item[0]
748 if not isinstance(item, basestring):
749 is_numerically_iterable = True
750 else:
751 is_numerically_iterable = False
752 except TypeError:
753 is_numerically_iterable = False
754
755 if is_numerically_iterable:
756
757
758 col_val = unicode(item[0])
759 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
760 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
761 col_val = unicode(item[col_idx])
762 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
763 else:
764
765 col_val = unicode(item)
766 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
767
769 """<data must be a list corresponding to the item indices>"""
770 self.__data = data
771 self.__tt_last_item = None
772
774 self.Select(0, on = 0)
775 for idx in selections:
776 self.Select(idx = idx, on = 1)
777
778
779
780
782 labels = []
783 for col_idx in self.GetColumnCount():
784 col = self.GetColumn(col = col_idx)
785 labels.append(col.GetText())
786 return labels
787
789 if item_idx is not None:
790 return self.GetItem(item_idx)
791
793 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
794
796 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
797
799
800 if self.__is_single_selection or only_one:
801 return self.GetFirstSelected()
802
803 items = []
804 idx = self.GetFirstSelected()
805 while idx != -1:
806 items.append(idx)
807 idx = self.GetNextSelected(idx)
808
809 return items
810
812
813 if self.__is_single_selection or only_one:
814 return self.GetItemText(self.GetFirstSelected())
815
816 items = []
817 idx = self.GetFirstSelected()
818 while idx != -1:
819 items.append(self.GetItemText(idx))
820 idx = self.GetNextSelected(idx)
821
822 return items
823
825 if self.__data is None:
826 return None
827
828 if item_idx is not None:
829 return self.__data[item_idx]
830
831 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
832
834
835 if self.__is_single_selection or only_one:
836 if self.__data is None:
837 return None
838 idx = self.GetFirstSelected()
839 if idx == -1:
840 return None
841 return self.__data[idx]
842
843 data = []
844 if self.__data is None:
845 return data
846 idx = self.GetFirstSelected()
847 while idx != -1:
848 data.append(self.__data[idx])
849 idx = self.GetNextSelected(idx)
850
851 return data
852
854 self.Select(idx = self.GetFirstSelected(), on = 0)
855
857 self.DeleteItem(item_idx)
858 if self.__data is not None:
859 del self.__data[item_idx]
860 self.__tt_last_item = None
861
862
863
865 event.Skip()
866 if self.__activate_callback is not None:
867 self.__activate_callback(event)
868
870 item_idx, where = self.HitTest(wx.Point(event.X, event.Y))
871
872 if self.__tt_last_item == item_idx:
873 return
874
875 self.__tt_last_item = item_idx
876
877 if item_idx == -1:
878 self.SetToolTipString(self.__tt_static_part)
879 return
880
881 dyna_tt = None
882 if self.__item_tooltip_callback is not None:
883 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
884
885 if dyna_tt is None:
886 self.SetToolTipString(self.__tt_static_part)
887 return
888
889 self.SetToolTipString(dyna_tt)
890
891
892
894 return self.__activate_callback
895
897 if callback is None:
898 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
899 else:
900 if not callable(callback):
901 raise ValueError('<activate> callback is not a callable: %s' % callback)
902 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
903 self.__activate_callback = callback
904
905 activate_callback = property(_get_activate_callback, _set_activate_callback)
906
912
913 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
914
915
916
917 if __name__ == '__main__':
918
919 if len(sys.argv) < 2:
920 sys.exit()
921
922 if sys.argv[1] != 'test':
923 sys.exit()
924
925 from Gnumed.pycommon import gmI18N
926 gmI18N.activate_locale()
927 gmI18N.install_domain()
928
929
931 app = wx.PyWidgetTester(size = (400, 500))
932 dlg = wx.MultiChoiceDialog (
933 parent = None,
934 message = 'test message',
935 caption = 'test caption',
936 choices = ['a', 'b', 'c', 'd', 'e']
937 )
938 dlg.ShowModal()
939 sels = dlg.GetSelections()
940 print "selected:"
941 for sel in sels:
942 print sel
943
945
946 def edit(argument):
947 print "editor called with:"
948 print argument
949
950 def refresh(lctrl):
951 choices = ['a', 'b', 'c']
952 lctrl.set_string_items(choices)
953
954 app = wx.PyWidgetTester(size = (200, 50))
955 chosen = get_choices_from_list (
956
957 caption = 'select health issues',
958
959
960 columns = ['issue'],
961 refresh_callback = refresh
962
963 )
964 print "chosen:"
965 print chosen
966
968 app = wx.PyWidgetTester(size = (200, 50))
969 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
970 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
971
972 dlg.set_string_items(['patient', 'emr', 'docs'])
973 result = dlg.ShowModal()
974 print result
975 print dlg.get_picks()
976
977
978
979 test_item_picker_dlg()
980
981
982
983