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
151
154
157
162
165
168
171
172
173
175 if not self.__ignore_OK_button:
176 self._BTN_ok.SetDefault()
177 self._BTN_ok.Enable(True)
178
179 if self.edit_callback is not None:
180 self._BTN_edit.Enable(True)
181
182 if self.delete_callback is not None:
183 self._BTN_delete.Enable(True)
184
186 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
187 if not self.can_return_empty:
188 self._BTN_cancel.SetDefault()
189 self._BTN_ok.Enable(False)
190 self._BTN_edit.Enable(False)
191 self._BTN_delete.Enable(False)
192
204
218
235
248
261
274
275
276
285
286 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
287
302
303 left_extra_button = property(lambda x:x, _set_left_extra_button)
304
319
320 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
321
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
424 """A panel holding a generic multi-column list and action buttions."""
425
445
446
447
450
452 self._LCTRL_items.set_string_items(items = items)
453 self._LCTRL_items.set_column_widths()
454
455 if (items is None) or (len(items) == 0):
456 self._BTN_edit.Enable(False)
457 self._BTN_remove.Enable(False)
458 else:
459 self._LCTRL_items.Select(0)
460
463
466
469
470
471
473 if self.edit_callback is not None:
474 self._BTN_edit.Enable(True)
475 if self.delete_callback is not None:
476 self._BTN_remove.Enable(True)
477
479 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
480 self._BTN_edit.Enable(False)
481 self._BTN_remove.Enable(False)
482
493
507
521
522
523
525 return self.__new_callback
526
528 self.__new_callback = callback
529 self._BTN_add.Enable(callback is not None)
530
531 new_callback = property(_get_new_callback, _set_new_callback)
532
533 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
534
536
538
539 try:
540 msg = kwargs['msg']
541 del kwargs['msg']
542 except KeyError:
543 msg = None
544
545 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
546
547 if msg is None:
548 self._LBL_msg.Hide()
549 else:
550 self._LBL_msg.SetLabel(msg)
551
552 self._LCTRL_left.activate_callback = self.__pick_selected
553
554
555 self._LCTRL_left.SetFocus()
556
557
558
559 - def set_columns(self, columns=None, columns_right=None):
560 self._LCTRL_left.set_columns(columns = columns)
561 if columns_right is None:
562 self._LCTRL_right.set_columns(columns = columns)
563 else:
564 if len(columns_right) < len(columns):
565 cols = columns
566 else:
567 cols = columns_right[:len(columns)]
568 self._LCTRL_right.set_columns(columns = cols)
569
577
580
585
591
594
597
598
599
619
621 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
622 return
623
624 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
625 self._LCTRL_right.remove_item(item_idx)
626
627 if self._LCTRL_right.GetItemCount() == 0:
628 self._BTN_right2left.Enable(False)
629
630
631
633 self._BTN_left2right.Enable(True)
634
636 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
637 self._BTN_left2right.Enable(False)
638
640 self._BTN_right2left.Enable(True)
641
643 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
644 self._BTN_right2left.Enable(False)
645
648
651
653
654
655
657
658 try:
659 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
660 except KeyError:
661 kwargs['style'] = wx.LC_REPORT
662
663 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
664
665 wx.ListCtrl.__init__(self, *args, **kwargs)
666 listmixins.ListCtrlAutoWidthMixin.__init__(self)
667
668 self.__widths = None
669 self.__data = None
670 self.__activate_callback = None
671
672 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
673 self.__item_tooltip_callback = None
674 self.__tt_last_item = None
675 self.__tt_static_part = _("""Select the items you want to work on.
676
677 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.""")
678
679
680
682 """(Re)define the columns.
683
684 Note that this will (have to) delete the items.
685 """
686 self.ClearAll()
687 self.__tt_last_item = None
688 if columns is None:
689 return
690 for idx in range(len(columns)):
691 self.InsertColumn(idx, columns[idx])
692
694 """Set the column width policy.
695
696 widths = None:
697 use previous policy if any or default policy
698 widths != None:
699 use this policy and remember it for later calls
700
701 This means there is no way to *revert* to the default policy :-(
702 """
703
704 if widths is not None:
705 self.__widths = widths
706 for idx in range(len(self.__widths)):
707 self.SetColumnWidth(col = idx, width = self.__widths[idx])
708 return
709
710
711 if self.__widths is not None:
712 for idx in range(len(self.__widths)):
713 self.SetColumnWidth(col = idx, width = self.__widths[idx])
714 return
715
716
717 if self.GetItemCount() == 0:
718 width_type = wx.LIST_AUTOSIZE_USEHEADER
719 else:
720 width_type = wx.LIST_AUTOSIZE
721 for idx in range(self.GetColumnCount()):
722 self.SetColumnWidth(col = idx, width = width_type)
723
725 """All item members must be unicode()able or None."""
726
727 self.DeleteAllItems()
728 self.__data = items
729 self.__tt_last_item = None
730
731 if items is None:
732 return
733
734 for item in items:
735 try:
736 item[0]
737 if not isinstance(item, basestring):
738 is_numerically_iterable = True
739 else:
740 is_numerically_iterable = False
741 except TypeError:
742 is_numerically_iterable = False
743
744 if is_numerically_iterable:
745
746
747 col_val = unicode(item[0])
748 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
749 for col_idx in range(1, min(self.GetColumnCount(), len(item))):
750 col_val = unicode(item[col_idx])
751 self.SetStringItem(index = row_num, col = col_idx, label = col_val)
752 else:
753
754 col_val = unicode(item)
755 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
756
758 """<data must be a list corresponding to the item indices>"""
759 self.__data = data
760 self.__tt_last_item = None
761
763 self.Select(0, on = 0)
764 for idx in selections:
765 self.Select(idx = idx, on = 1)
766
767
768
769
771 labels = []
772 for col_idx in self.GetColumnCount():
773 col = self.GetColumn(col = col_idx)
774 labels.append(col.GetText())
775 return labels
776
778 if item_idx is not None:
779 return self.GetItem(item_idx)
780
782 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
783
785 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
786
788
789 if self.__is_single_selection or only_one:
790 return self.GetFirstSelected()
791
792 items = []
793 idx = self.GetFirstSelected()
794 while idx != -1:
795 items.append(idx)
796 idx = self.GetNextSelected(idx)
797
798 return items
799
801
802 if self.__is_single_selection or only_one:
803 return self.GetItemText(self.GetFirstSelected())
804
805 items = []
806 idx = self.GetFirstSelected()
807 while idx != -1:
808 items.append(self.GetItemText(idx))
809 idx = self.GetNextSelected(idx)
810
811 return items
812
814 if self.__data is None:
815 return None
816
817 if item_idx is not None:
818 return self.__data[item_idx]
819
820 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
821
823
824 if self.__is_single_selection or only_one:
825 if self.__data is None:
826 return None
827 idx = self.GetFirstSelected()
828 if idx == -1:
829 return None
830 return self.__data[idx]
831
832 data = []
833 if self.__data is None:
834 return data
835 idx = self.GetFirstSelected()
836 while idx != -1:
837 data.append(self.__data[idx])
838 idx = self.GetNextSelected(idx)
839
840 return data
841
843 self.Select(idx = self.GetFirstSelected(), on = 0)
844
846 self.DeleteItem(item_idx)
847 if self.__data is not None:
848 del self.__data[item_idx]
849 self.__tt_last_item = None
850
851
852
854 event.Skip()
855 if self.__activate_callback is not None:
856 self.__activate_callback(event)
857
859 item_idx, where = self.HitTest(wx.Point(event.X, event.Y))
860
861 if self.__tt_last_item == item_idx:
862 return
863
864 self.__tt_last_item = item_idx
865
866 if item_idx == -1:
867 self.SetToolTipString(self.__tt_static_part)
868 return
869
870 dyna_tt = None
871 if self.__item_tooltip_callback is not None:
872 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx])
873
874 if dyna_tt is None:
875 self.SetToolTipString(self.__tt_static_part)
876 return
877
878 self.SetToolTipString(dyna_tt)
879
880
881
883 return self.__activate_callback
884
886 if callback is None:
887 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
888 else:
889 if not callable(callback):
890 raise ValueError('<activate> callback is not a callable: %s' % callback)
891 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
892 self.__activate_callback = callback
893
894 activate_callback = property(_get_activate_callback, _set_activate_callback)
895
901
902 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
903
904
905
906 if __name__ == '__main__':
907
908 if len(sys.argv) < 2:
909 sys.exit()
910
911 if sys.argv[1] != 'test':
912 sys.exit()
913
914 from Gnumed.pycommon import gmI18N
915 gmI18N.activate_locale()
916 gmI18N.install_domain()
917
918
920 app = wx.PyWidgetTester(size = (400, 500))
921 dlg = wx.MultiChoiceDialog (
922 parent = None,
923 message = 'test message',
924 caption = 'test caption',
925 choices = ['a', 'b', 'c', 'd', 'e']
926 )
927 dlg.ShowModal()
928 sels = dlg.GetSelections()
929 print "selected:"
930 for sel in sels:
931 print sel
932
934
935 def edit(argument):
936 print "editor called with:"
937 print argument
938
939 def refresh(lctrl):
940 choices = ['a', 'b', 'c']
941 lctrl.set_string_items(choices)
942
943 app = wx.PyWidgetTester(size = (200, 50))
944 chosen = get_choices_from_list (
945
946 caption = 'select health issues',
947
948
949 columns = ['issue'],
950 refresh_callback = refresh
951
952 )
953 print "chosen:"
954 print chosen
955
957 app = wx.PyWidgetTester(size = (200, 50))
958 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
959 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
960
961 dlg.set_string_items(['patient', 'emr', 'docs'])
962 result = dlg.ShowModal()
963 print result
964 print dlg.get_picks()
965
966
967
968 test_item_picker_dlg()
969
970
971
972