| Home | Trees | Indices | Help |
|
|---|
|
|
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 sorting: http://code.activestate.com/recipes/426407/
15 """
16 #================================================================
17 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
18 __license__ = "GPL v2 or later"
19
20
21 import sys
22 import types
23 import logging
24 import thread
25 import time
26 import re as regex
27
28
29 import wx
30 import wx.lib.mixins.listctrl as listmixins
31
32
33 _log = logging.getLogger('gm.list_ui')
34 #================================================================
35 # FIXME: configurable callback on double-click action
36
37 -def get_choices_from_list (
38 parent=None,
39 msg=None,
40 caption=None,
41 columns=None,
42 choices=None,
43 data=None,
44 selections=None,
45 edit_callback=None,
46 new_callback=None,
47 delete_callback=None,
48 refresh_callback=None,
49 single_selection=False,
50 can_return_empty=False,
51 ignore_OK_button=False,
52 left_extra_button=None,
53 middle_extra_button=None,
54 right_extra_button=None,
55 list_tooltip_callback=None):
56 """Let user select item(s) from a list.
57
58 - new_callback: ()
59 - edit_callback: (item data)
60 - delete_callback: (item data)
61 - refresh_callback: (listctrl)
62 - list_tooltip_callback: (item data)
63
64 - left/middle/right_extra_button: (label, tooltip, <callback>)
65 <callback> is called with item_data as the only argument
66
67 returns:
68 on [CANCEL]: None
69 on [OK]:
70 if any items selected:
71 list of selected items
72 else:
73 if can_return_empty is True:
74 empty list
75 else:
76 None
77 """
78 if caption is None:
79 caption = _('generic multi choice dialog')
80
81 if single_selection:
82 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
83 else:
84 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
85
86 dlg.refresh_callback = refresh_callback
87 dlg.edit_callback = edit_callback
88 dlg.new_callback = new_callback
89 dlg.delete_callback = delete_callback
90 dlg.list_tooltip_callback = list_tooltip_callback
91
92 dlg.ignore_OK_button = ignore_OK_button
93 dlg.left_extra_button = left_extra_button
94 dlg.middle_extra_button = middle_extra_button
95 dlg.right_extra_button = right_extra_button
96
97 dlg.set_columns(columns = columns)
98
99 if refresh_callback is None:
100 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible
101 dlg.set_column_widths()
102
103 if data is not None:
104 dlg.set_data(data = data) # can override data set if refresh_callback is not None
105
106 if selections is not None:
107 dlg.set_selections(selections = selections)
108 dlg.can_return_empty = can_return_empty
109
110 btn_pressed = dlg.ShowModal()
111 sels = dlg.get_selected_item_data(only_one = single_selection)
112 dlg.Destroy()
113
114 if btn_pressed == wx.ID_OK:
115 if can_return_empty and (sels is None):
116 return []
117 return sels
118
119 return None
120 #----------------------------------------------------------------
121 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
122
124 """A dialog holding a list and a few buttons to act on the items."""
125
126 # FIXME: configurable callback on double-click action
127
129
130 try:
131 msg = kwargs['msg']
132 del kwargs['msg']
133 except KeyError: msg = None
134
135 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
136
137 self.message = msg
138
139 self.left_extra_button = None
140 self.middle_extra_button = None
141 self.right_extra_button = None
142
143 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
144 self.new_callback = None # called when NEW button pressed, no argument passed in
145 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
146 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
147
148 self.can_return_empty = False
149 self.ignore_OK_button = False # by default do show/use the OK button
150 #------------------------------------------------------------
152 self._LCTRL_items.set_columns(columns = columns)
153 #------------------------------------------------------------
155 self._LCTRL_items.set_column_widths(widths = widths)
156 #------------------------------------------------------------
158 self._LCTRL_items.set_string_items(items = items)
159 self._LCTRL_items.set_column_widths()
160 self._LCTRL_items.Select(0)
161 #------------------------------------------------------------
163 self._LCTRL_items.set_selections(selections = selections)
164 if selections is None:
165 return
166 if len(selections) == 0:
167 return
168 if self.ignore_OK_button:
169 return
170 self._BTN_ok.Enable(True)
171 self._BTN_ok.SetDefault()
172 #------------------------------------------------------------
175 #------------------------------------------------------------
177 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
178 #------------------------------------------------------------
179 # event handlers
180 #------------------------------------------------------------
182 if not self.__ignore_OK_button:
183 self._BTN_ok.SetDefault()
184 self._BTN_ok.Enable(True)
185
186 if self.edit_callback is not None:
187 self._BTN_edit.Enable(True)
188
189 if self.delete_callback is not None:
190 self._BTN_delete.Enable(True)
191 #------------------------------------------------------------
193 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
194 if not self.can_return_empty:
195 self._BTN_cancel.SetDefault()
196 self._BTN_ok.Enable(False)
197 self._BTN_edit.Enable(False)
198 self._BTN_delete.Enable(False)
199 #------------------------------------------------------------
214 #------------------------------------------------------------
231 #------------------------------------------------------------
252 #------------------------------------------------------------
268 #------------------------------------------------------------
284 #------------------------------------------------------------
300 #------------------------------------------------------------
301 # properties
302 #------------------------------------------------------------
316
317 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
318 #------------------------------------------------------------
334
335 left_extra_button = property(lambda x:x, _set_left_extra_button)
336 #------------------------------------------------------------
352
353 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
354 #------------------------------------------------------------
370
371 right_extra_button = property(lambda x:x, _set_right_extra_button)
372 #------------------------------------------------------------
375
377 if callback is not None:
378 if self.refresh_callback is None:
379 raise ValueError('refresh callback must be set before new callback can be set')
380 if not callable(callback):
381 raise ValueError('<new> callback is not a callable: %s' % callback)
382 self.__new_callback = callback
383
384 if callback is None:
385 self._BTN_new.Enable(False)
386 self._BTN_new.Hide()
387 else:
388 self._BTN_new.Enable(True)
389 self._BTN_new.Show()
390
391 new_callback = property(_get_new_callback, _set_new_callback)
392 #------------------------------------------------------------
395
397 if callback is not None:
398 if not callable(callback):
399 raise ValueError('<edit> callback is not a callable: %s' % callback)
400 self.__edit_callback = callback
401
402 if callback is None:
403 self._BTN_edit.Enable(False)
404 self._BTN_edit.Hide()
405 else:
406 self._BTN_edit.Enable(True)
407 self._BTN_edit.Show()
408
409 edit_callback = property(_get_edit_callback, _set_edit_callback)
410 #------------------------------------------------------------
413
415 if callback is not None:
416 if self.refresh_callback is None:
417 raise ValueError('refresh callback must be set before delete callback can be set')
418 if not callable(callback):
419 raise ValueError('<delete> callback is not a callable: %s' % callback)
420 self.__delete_callback = callback
421
422 if callback is None:
423 self._BTN_delete.Enable(False)
424 self._BTN_delete.Hide()
425 else:
426 self._BTN_delete.Enable(True)
427 self._BTN_delete.Show()
428
429 delete_callback = property(_get_delete_callback, _set_delete_callback)
430 #------------------------------------------------------------
433
435 wx.BeginBusyCursor()
436 try:
437 self.refresh_callback(lctrl = self._LCTRL_items)
438 finally:
439 wx.EndBusyCursor()
440 self._LCTRL_items.set_column_widths()
441
443 if callback is not None:
444 if not callable(callback):
445 raise ValueError('<refresh> callback is not a callable: %s' % callback)
446 self.__refresh_callback = callback
447 if callback is not None:
448 wx.CallAfter(self._set_refresh_callback_helper)
449
450 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
451 #------------------------------------------------------------
453 self._LCTRL_items.item_tooltip_callback = callback
454
455 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
456 #def _get_tooltip(self, item): # inside a class
457 #def _get_tooltip(item): # outside a class
458 #------------------------------------------------------------
460 if message is None:
461 self._LBL_message.Hide()
462 return
463 self._LBL_message.SetLabel(message)
464 self._LBL_message.Show()
465
466 message = property(lambda x:x, _set_message)
467 #================================================================
468 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
469
471 """A panel holding a generic multi-column list and action buttions."""
472
474
475 try:
476 msg = kwargs['msg']
477 del kwargs['msg']
478 except KeyError: msg = None
479
480 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
481
482 if msg is None:
483 self._LBL_message.Hide()
484 else:
485 self._LBL_message.SetLabel(msg)
486
487 # new/edit/delete must return True/False to enable refresh
488 self.__new_callback = None # called when NEW button pressed, no argument passed in
489 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in
490 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in
491 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
492
493 self.__select_callback = None # called when an item is selected, data of topmost selected item passed in
494
495 self.left_extra_button = None
496 self.middle_extra_button = None
497 self.right_extra_button = None
498 #------------------------------------------------------------
499 # external API
500 #------------------------------------------------------------
502 self._LCTRL_items.set_columns(columns = columns)
503 #------------------------------------------------------------
505 self._LCTRL_items.set_string_items(items = items)
506 self._LCTRL_items.set_column_widths()
507
508 if (items is None) or (len(items) == 0):
509 self._BTN_edit.Enable(False)
510 self._BTN_remove.Enable(False)
511 else:
512 self._LCTRL_items.Select(0)
513 #------------------------------------------------------------
516 #------------------------------------------------------------
519 #------------------------------------------------------------
521 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
522 #------------------------------------------------------------
523 # event handlers
524 #------------------------------------------------------------
526 if self.edit_callback is not None:
527 self._BTN_edit.Enable(True)
528 if self.delete_callback is not None:
529 self._BTN_remove.Enable(True)
530 if self.__select_callback is not None:
531 item = self._LCTRL_items.get_selected_item_data(only_one=True)
532 self.__select_callback(item)
533 #------------------------------------------------------------
535 if self._LCTRL_items.get_selected_items(only_one=True) == -1:
536 self._BTN_edit.Enable(False)
537 self._BTN_remove.Enable(False)
538 if self.__select_callback is not None:
539 self.__select_callback(None)
540 #------------------------------------------------------------
551 #------------------------------------------------------------
556 #------------------------------------------------------------
570 #------------------------------------------------------------
584 #------------------------------------------------------------
600 #------------------------------------------------------------
616 #------------------------------------------------------------
632 #------------------------------------------------------------
633 # properties
634 #------------------------------------------------------------
637
639 if callback is not None:
640 if not callable(callback):
641 raise ValueError('<new> callback is not a callable: %s' % callback)
642 self.__new_callback = callback
643 self._BTN_add.Enable(callback is not None)
644
645 new_callback = property(_get_new_callback, _set_new_callback)
646 #------------------------------------------------------------
649
651 if callback is not None:
652 if not callable(callback):
653 raise ValueError('<select> callback is not a callable: %s' % callback)
654 self.__select_callback = callback
655
656 select_callback = property(_get_select_callback, _set_select_callback)
657 #------------------------------------------------------------
660
662 if msg is None:
663 self._LBL_message.Hide()
664 self._LBL_message.SetLabel(u'')
665 else:
666 self._LBL_message.SetLabel(msg)
667 self._LBL_message.Show()
668 self.Layout()
669
670 message = property(_get_message, _set_message)
671 #------------------------------------------------------------
687
688 left_extra_button = property(lambda x:x, _set_left_extra_button)
689 #------------------------------------------------------------
705
706 middle_extra_button = property(lambda x:x, _set_middle_extra_button)
707 #------------------------------------------------------------
723
724 right_extra_button = property(lambda x:x, _set_right_extra_button)
725 #================================================================
726 from Gnumed.wxGladeWidgets import wxgItemPickerDlg
727
729
731
732 try:
733 msg = kwargs['msg']
734 del kwargs['msg']
735 except KeyError:
736 msg = None
737
738 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
739
740 if msg is None:
741 self._LBL_msg.Hide()
742 else:
743 self._LBL_msg.SetLabel(msg)
744
745 self.allow_duplicate_picks = True
746
747 self._LCTRL_left.activate_callback = self.__pick_selected
748 self.__extra_button_callback = None
749
750 self._LCTRL_left.SetFocus()
751 #------------------------------------------------------------
752 # external API
753 #------------------------------------------------------------
755 self._LCTRL_left.set_columns(columns = columns)
756 if columns_right is None:
757 self._LCTRL_right.set_columns(columns = columns)
758 else:
759 if len(columns_right) < len(columns):
760 cols = columns
761 else:
762 cols = columns_right[:len(columns)]
763 self._LCTRL_right.set_columns(columns = cols)
764 #------------------------------------------------------------
766 self._LCTRL_left.set_string_items(items = items)
767 self._LCTRL_left.set_column_widths()
768 self._LCTRL_right.set_string_items()
769
770 self._BTN_left2right.Enable(False)
771 self._BTN_right2left.Enable(False)
772 #------------------------------------------------------------
775 #------------------------------------------------------------
780 #------------------------------------------------------------
782 self._LCTRL_right.set_string_items(picks)
783 self._LCTRL_right.set_column_widths()
784 if data is not None:
785 self._LCTRL_right.set_data(data = data)
786 #------------------------------------------------------------
789 #------------------------------------------------------------
791 return self._LCTRL_right.get_item_data()
792
793 picks = property(get_picks, lambda x:x)
794 #------------------------------------------------------------
810
811 extra_button = property(lambda x:x, _set_extra_button)
812 #------------------------------------------------------------
813 # internal helpers
814 #------------------------------------------------------------
816 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
817 return
818
819 right_items = self._LCTRL_right.get_string_items()
820 right_data = self._LCTRL_right.get_item_data()
821 if right_data is None:
822 right_data = []
823
824 selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
825 selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
826
827 if self.allow_duplicate_picks:
828 right_items.extend(selected_items)
829 right_data.extend(selected_data)
830 self._LCTRL_right.set_string_items(items = right_items)
831 self._LCTRL_right.set_data(data = right_data)
832 self._LCTRL_right.set_column_widths()
833 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
834 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
835 return
836
837 for sel_item, sel_data in zip(selected_items, selected_data):
838 if sel_item in right_items:
839 continue
840 right_items.append(sel_item)
841 right_data.append(sel_data)
842 self._LCTRL_right.set_string_items(items = right_items)
843 self._LCTRL_right.set_data(data = right_data)
844 self._LCTRL_right.set_column_widths()
845 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
846 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
847 #------------------------------------------------------------
849 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
850 return
851
852 for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
853 self._LCTRL_right.remove_item(item_idx)
854
855 if self._LCTRL_right.GetItemCount() == 0:
856 self._BTN_right2left.Enable(False)
857
858 # print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
859 # print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
860 #------------------------------------------------------------
861 # event handlers
862 #------------------------------------------------------------
865 #------------------------------------------------------------
867 if self._LCTRL_left.get_selected_items(only_one = True) == -1:
868 self._BTN_left2right.Enable(False)
869 #------------------------------------------------------------
872 #------------------------------------------------------------
874 if self._LCTRL_right.get_selected_items(only_one = True) == -1:
875 self._BTN_right2left.Enable(False)
876 #------------------------------------------------------------
879 #------------------------------------------------------------
882 #------------------------------------------------------------
885 #------------------------------------------------------------
887 self._LCTRL_left.item_tooltip_callback = callback
888
889 left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
890 #------------------------------------------------------------
892 self._LCTRL_right.item_tooltip_callback = callback
893
894 right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
895
896 #================================================================
897 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin):
898
899 # sorting: at set_string_items() time all items will be
900 # adorned with their initial row number as wxPython data,
901 # this is used later for a) sorting and b) to access
902 # GNUmed data objects associated with rows,
903 # the latter are ordered in initial row number order
904 # at set_data() time
905
906 map_item_idx2data_idx = wx.ListCtrl.GetItemData
907
908 sort_order_tags = {
909 True: u' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
910 False: u' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
911 }
912
914
915 self.debug = None
916
917 try:
918 kwargs['style'] = kwargs['style'] | wx.LC_REPORT
919 except KeyError:
920 kwargs['style'] = wx.LC_REPORT
921
922 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
923
924 wx.ListCtrl.__init__(self, *args, **kwargs)
925 listmixins.ListCtrlAutoWidthMixin.__init__(self)
926
927 # required for column sorting, MUST have this name
928 self._invalidate_sorting_metadata() # must be called after each (external/direct) list item update
929 listmixins.ColumnSorterMixin.__init__(self, 0) # must be called again after adding columns (why ?)
930 # for debugging sorting:
931 #self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
932
933 self.__widths = None
934 self.__data = None
935 self.__activate_callback = None
936 self.__rightclick_callback = None
937
938 self.__item_tooltip_callback = None
939 self.__tt_last_item = None
940 self.__tt_static_part = _("""Select the items you want to work on.
941
942 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.""")
943 self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
944
945 self.__next_line_to_search = 0
946 self.__search_data = None
947 self.__search_dlg = None
948 self.__searchable_cols = None
949 # self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
950 self.Bind(wx.EVT_CHAR, self._on_char)
951 self.Bind(wx.EVT_FIND_CLOSE, self._on_search_dlg_closed)
952 self.Bind(wx.EVT_FIND, self._on_search_first_match)
953 self.Bind(wx.EVT_FIND_NEXT, self._on_search_next_match)
954 #------------------------------------------------------------
955 # setters
956 #------------------------------------------------------------
958 """(Re)define the columns.
959
960 Note that this will (have to) delete the items.
961 """
962 self.ClearAll()
963 self.__tt_last_item = None
964 if columns is None:
965 return
966 for idx in range(len(columns)):
967 self.InsertColumn(idx, columns[idx])
968
969 self._invalidate_sorting_metadata()
970 #------------------------------------------------------------
972 """Set the column width policy.
973
974 widths = None:
975 use previous policy if any or default policy
976 widths != None:
977 use this policy and remember it for later calls
978
979 This means there is no way to *revert* to the default policy :-(
980 """
981 # explicit policy ?
982 if widths is not None:
983 self.__widths = widths
984 for idx in range(len(self.__widths)):
985 self.SetColumnWidth(col = idx, width = self.__widths[idx])
986 return
987
988 # previous policy ?
989 if self.__widths is not None:
990 for idx in range(len(self.__widths)):
991 self.SetColumnWidth(col = idx, width = self.__widths[idx])
992 return
993
994 # default policy !
995 if self.GetItemCount() == 0:
996 width_type = wx.LIST_AUTOSIZE_USEHEADER
997 else:
998 width_type = wx.LIST_AUTOSIZE
999 for idx in range(self.GetColumnCount()):
1000 self.SetColumnWidth(col = idx, width = width_type)
1001 #------------------------------------------------------------
1003 """All item members must be unicode()able or None."""
1004
1005 wx.BeginBusyCursor()
1006 self._invalidate_sorting_metadata()
1007
1008 # remove existing items
1009 loop = 0
1010 while True:
1011 if loop > 3:
1012 _log.debug('unable to delete list items after looping 3 times, continuing and hoping for the best')
1013 break
1014 loop += 1
1015 if self.debug is not None:
1016 _log.debug('[round %s] GetItemCount() before DeleteAllItems(): %s (%s, thread [%s])', loop, self.GetItemCount(), self.debug, thread.get_ident())
1017 if not self.DeleteAllItems():
1018 _log.debug('DeleteAllItems() failed (%s)', self.debug)
1019 item_count = self.GetItemCount()
1020 if self.debug is not None:
1021 _log.debug('GetItemCount() after DeleteAllItems(): %s (%s)', item_count, self.debug)
1022 if item_count == 0:
1023 break
1024 wx.SafeYield(None, True)
1025 _log.debug('GetItemCount() not 0 after DeleteAllItems() (%s)', self.debug)
1026 time.sleep(0.3)
1027 wx.SafeYield(None, True)
1028
1029 if items is None:
1030 self.data = None
1031 wx.EndBusyCursor()
1032 return
1033
1034 # insert new items
1035 for item in items:
1036 try:
1037 item[0]
1038 if not isinstance(item, basestring):
1039 is_numerically_iterable = True
1040 # do not iterate over individual chars in a string, however
1041 else:
1042 is_numerically_iterable = False
1043 except TypeError:
1044 is_numerically_iterable = False
1045
1046 if is_numerically_iterable:
1047 # cannot use errors='replace' since then
1048 # None/ints/unicode strings fail to get encoded
1049 col_val = unicode(item[0])
1050 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1051 for col_num in range(1, min(self.GetColumnCount(), len(item))):
1052 col_val = unicode(item[col_num])
1053 self.SetStringItem(index = row_num, col = col_num, label = col_val)
1054 else:
1055 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded
1056 col_val = unicode(item)
1057 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
1058
1059 # set data to be a copy of items
1060 self.data = items
1061
1062 wx.EndBusyCursor()
1063 #------------------------------------------------------------
1065 """<data> assumed to be a list corresponding to the item indices"""
1066 if data is not None:
1067 item_count = self.GetItemCount()
1068 if len(data) != item_count:
1069 _log.debug('<data> length (%s) must be equal to number of list items (%s) (%s, thread [%s])', len(data), item_count, self.debug, thread.get_ident())
1070 for item_idx in range(len(data)):
1071 self.SetItemData(item_idx, item_idx)
1072 self.__data = data
1073 self.__tt_last_item = None
1074 # string data (rows/visible list items) not modified,
1075 # so no need to call _update_sorting_metadata
1076 return
1077
1079 # slower than "return self.__data" but helps with detecting
1080 # problems with len(__data)<>self.GetItemCount()
1081 return self.get_item_data() # returns all data if item_idx is None
1082
1083 data = property(_get_data, set_data)
1084 #------------------------------------------------------------
1086 self.Select(0, on = 0)
1087 if selections is None:
1088 return
1089 for idx in selections:
1090 self.Select(idx = idx, on = 1)
1091
1093 if self.__is_single_selection:
1094 return [self.GetFirstSelected()]
1095 selections = []
1096 idx = self.GetFirstSelected()
1097 while idx != -1:
1098 selections.append(idx)
1099 idx = self.GetNextSelected(idx)
1100 return selections
1101
1102 selections = property(__get_selections, set_selections)
1103 #------------------------------------------------------------
1104 # getters
1105 #------------------------------------------------------------
1107 labels = []
1108 for col_idx in self.GetColumnCount():
1109 col = self.GetColumn(col = col_idx)
1110 labels.append(col.GetText())
1111 return labels
1112 #------------------------------------------------------------
1116 #------------------------------------------------------------
1119 #------------------------------------------------------------
1122 #------------------------------------------------------------
1124
1125 if self.__is_single_selection or only_one:
1126 return self.GetFirstSelected()
1127
1128 items = []
1129 idx = self.GetFirstSelected()
1130 while idx != -1:
1131 items.append(idx)
1132 idx = self.GetNextSelected(idx)
1133
1134 return items
1135 #------------------------------------------------------------
1137
1138 if self.__is_single_selection or only_one:
1139 return self.GetItemText(self.GetFirstSelected())
1140
1141 items = []
1142 idx = self.GetFirstSelected()
1143 while idx != -1:
1144 items.append(self.GetItemText(idx))
1145 idx = self.GetNextSelected(idx)
1146
1147 return items
1148 #------------------------------------------------------------
1150 if self.__data is None: # this isn't entirely clean
1151 return None
1152
1153 if item_idx is not None:
1154 return self.__data[self.map_item_idx2data_idx(item_idx)]
1155
1156 # if <idx> is None return all data up to item_count,
1157 # in case of len(__data) <> self.GetItemCount() this
1158 # gives the chance to figure out what is going on
1159 return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
1160 #------------------------------------------------------------
1162
1163 if self.__is_single_selection or only_one:
1164 if self.__data is None:
1165 return None
1166 idx = self.GetFirstSelected()
1167 if idx == -1:
1168 return None
1169 return self.__data[self.map_item_idx2data_idx(idx)]
1170
1171 data = []
1172 if self.__data is None:
1173 return data
1174 idx = self.GetFirstSelected()
1175 while idx != -1:
1176 data.append(self.__data[self.map_item_idx2data_idx(idx)])
1177 idx = self.GetNextSelected(idx)
1178
1179 return data
1180 #------------------------------------------------------------
1182 self.Select(idx = self.GetFirstSelected(), on = 0)
1183 #------------------------------------------------------------
1185 # do NOT remove the corresponding data because even if
1186 # the item pointing to this data instance is gone all
1187 # other items will still point to their corresponding
1188 # *initial* row numbers
1189 #if self.__data is not None:
1190 # del self.__data[self.map_item_idx2data_idx(item_idx)]
1191 self.DeleteItem(item_idx)
1192 self.__tt_last_item = None
1193 self._invalidate_sorting_metadata()
1194 #------------------------------------------------------------
1195 # event handlers
1196 #------------------------------------------------------------
1198 event.Skip()
1199 if self.__activate_callback is not None:
1200 self.__activate_callback(event)
1201 #------------------------------------------------------------
1203 event.Skip()
1204 if self.__rightclick_callback is not None:
1205 self.__rightclick_callback(event)
1206 #------------------------------------------------------------
1208
1209 if evt.GetModifiers() != wx.MOD_CMD:
1210 evt.Skip()
1211 return
1212
1213 if unichr(evt.GetRawKeyCode()) != u'f':
1214 evt.Skip()
1215 return
1216
1217 if self.__search_dlg is not None:
1218 self.__search_dlg.Close()
1219 return
1220
1221 if len(self.__searchable_cols) == 0:
1222 return
1223
1224 if self.__search_data is None:
1225 self.__search_data = wx.FindReplaceData()
1226 self.__search_dlg = wx.FindReplaceDialog (
1227 self,
1228 self.__search_data,
1229 _('Search in list'),
1230 wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD
1231 )
1232 self.__search_dlg.Show(True)
1233 #------------------------------------------------------------
1235 """Update tooltip on mouse motion.
1236
1237 for s in dir(wx):
1238 if s.startswith('LIST_HITTEST'):
1239 print s, getattr(wx, s)
1240
1241 LIST_HITTEST_ABOVE 1
1242 LIST_HITTEST_BELOW 2
1243 LIST_HITTEST_NOWHERE 4
1244 LIST_HITTEST_ONITEM 672
1245 LIST_HITTEST_ONITEMICON 32
1246 LIST_HITTEST_ONITEMLABEL 128
1247 LIST_HITTEST_ONITEMRIGHT 256
1248 LIST_HITTEST_ONITEMSTATEICON 512
1249 LIST_HITTEST_TOLEFT 1024
1250 LIST_HITTEST_TORIGHT 2048
1251 """
1252 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
1253
1254 # pointer on item related area at all ?
1255 if where_flag not in [
1256 wx.LIST_HITTEST_ONITEMLABEL,
1257 wx.LIST_HITTEST_ONITEMICON,
1258 wx.LIST_HITTEST_ONITEMSTATEICON,
1259 wx.LIST_HITTEST_ONITEMRIGHT,
1260 wx.LIST_HITTEST_ONITEM
1261 ]:
1262 self.__tt_last_item = None # not on any item
1263 self.SetToolTipString(self.__tt_static_part)
1264 return
1265
1266 # same item as last time around ?
1267 if self.__tt_last_item == item_idx:
1268 return
1269
1270 # remeber the new item we are on
1271 self.__tt_last_item = item_idx
1272
1273 # HitTest() can return -1 if it so pleases, meaning that no item
1274 # was hit or else that maybe there aren't any items (empty list)
1275 if item_idx == wx.NOT_FOUND:
1276 self.SetToolTipString(self.__tt_static_part)
1277 return
1278
1279 # do we *have* item data ?
1280 if self.__data is None:
1281 self.SetToolTipString(self.__tt_static_part)
1282 return
1283
1284 # under some circumstances the item_idx returned
1285 # by HitTest() may be out of bounds with respect to
1286 # self.__data, this hints at a sync problem between
1287 # setting display items and associated data
1288 if (
1289 (item_idx > (len(self.__data) - 1))
1290 or
1291 (item_idx < -1)
1292 ):
1293 self.SetToolTipString(self.__tt_static_part)
1294 print "*************************************************************"
1295 print "GNUmed has detected an inconsistency with list item tooltips."
1296 print ""
1297 print "This is not a big problem and you can keep working."
1298 print ""
1299 print "However, please send us the following so we can fix GNUmed:"
1300 print ""
1301 print "item idx: %s" % item_idx
1302 print 'where flag: %s' % where_flag
1303 print 'data list length: %s' % len(self.__data)
1304 print "*************************************************************"
1305 return
1306
1307 dyna_tt = None
1308 if self.__item_tooltip_callback is not None:
1309 dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
1310
1311 if dyna_tt is None:
1312 self.SetToolTipString(self.__tt_static_part)
1313 return
1314
1315 self.SetToolTipString(dyna_tt)
1316 #------------------------------------------------------------
1317 # search related methods
1318 #------------------------------------------------------------
1322 #------------------------------------------------------------
1327 # print self.FindFocus()
1328 # print self.__search_dlg
1329 #self.__search_dlg.Close()
1330 #------------------------------------------------------------
1332 for row_idx in range(self.__next_line_to_search, self.ItemCount):
1333 for col_idx in range(self.ColumnCount):
1334 if col_idx not in self.__searchable_cols:
1335 continue
1336 col_val = self.GetItem(row_idx, col_idx).GetText()
1337 if regex.search(search_term, col_val, regex.U | regex.I) is not None:
1338 self.Select(row_idx)
1339 self.EnsureVisible(row_idx)
1340 if row_idx == self.ItemCount - 1:
1341 # wrap around
1342 self.__next_line_to_search = 0
1343 else:
1344 self.__next_line_to_search = row_idx + 1
1345 return True
1346 # wrap around
1347 self.__next_line_to_search = 0
1348 return False
1349 #------------------------------------------------------------
1352 #------------------------------------------------------------
1355 #------------------------------------------------------------
1356 # properties
1357 #------------------------------------------------------------
1360
1362 if callback is None:
1363 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
1364 else:
1365 if not callable(callback):
1366 raise ValueError('<activate> callback is not a callable: %s' % callback)
1367 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
1368 self.__activate_callback = callback
1369
1370 activate_callback = property(_get_activate_callback, _set_activate_callback)
1371 #------------------------------------------------------------
1374
1376 if callback is None:
1377 self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK)
1378 else:
1379 if not callable(callback):
1380 raise ValueError('<rightclick> callback is not a callable: %s' % callback)
1381 self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
1382 self.__rightclick_callback = callback
1383
1384 rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback)
1385 #------------------------------------------------------------
1387 if callback is not None:
1388 if not callable(callback):
1389 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
1390 self.__item_tooltip_callback = callback
1391
1392 # the callback must be a function which takes a single argument
1393 # the argument is the data for the item the tooltip is on
1394 # the callback must return None if no item tooltip is to be shown
1395 # otherwise it must return a string (possibly with \n)
1396 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1397 #------------------------------------------------------------
1399 # zero-based list of which columns to search
1400 if cols is None:
1401 self.__searchable_cols = range(self.ColumnCount)
1402 return
1403 # weed out columns to be searched which
1404 # don't exist an uniquify them
1405 new_cols = {}
1406 for col in cols:
1407 if col < self.ColumnCount:
1408 new_cols[col] = True
1409 self.__searchable_cols = new_cols.keys()
1410
1411 searchable_columns = property(lambda x:x, _set_searchable_cols)
1412 #------------------------------------------------------------
1413 # ColumnSorterMixin API
1414 #------------------------------------------------------------
1419 #------------------------------------------------------------
1421 self._cleanup_column_headers()
1422 # annotate sort column
1423 col_idx, is_ascending = self.GetSortState()
1424 col_state = self.GetColumn(col_idx)
1425 col_state.m_text += self.sort_order_tags[is_ascending]
1426 self.SetColumn(col_idx, col_state)
1427 #------------------------------------------------------------
1429 dict2sort = {}
1430 item_count = self.GetItemCount()
1431 if item_count == 0:
1432 return dict2sort
1433 col_count = self.GetColumnCount()
1434 for item_idx in range(item_count):
1435 dict2sort[item_idx] = ()
1436 if col_count == 0:
1437 continue
1438 for col_idx in range(col_count):
1439 dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
1440
1441 return dict2sort
1442 #------------------------------------------------------------
1444 for col_idx in range(self.ColumnCount):
1445 col_state = self.GetColumn(col_idx)
1446 if col_state.m_text.endswith(self.sort_order_tags[True]):
1447 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[True])]
1448 if col_state.m_text.endswith(self.sort_order_tags[False]):
1449 col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[False])]
1450 self.SetColumn(col_idx, col_state)
1451 #------------------------------------------------------------
1453 self.itemDataMap = None
1454 self.SetColumnCount(self.GetColumnCount())
1455 self._cleanup_column_headers()
1456 #------------------------------------------------------------
1459 #------------------------------------------------------------
1461 # for debugging:
1462 # print "column clicked : %s" % (event.GetColumn())
1463 # column, order = self.GetSortState()
1464 # print "column %s sort %s" % (column, order)
1465 # print self._colSortFlag
1466 # print self.itemDataMap
1467 event.Skip()
1468
1469 #================================================================
1470 # main
1471 #----------------------------------------------------------------
1472 if __name__ == '__main__':
1473
1474 if len(sys.argv) < 2:
1475 sys.exit()
1476
1477 if sys.argv[1] != 'test':
1478 sys.exit()
1479
1480 sys.path.insert(0, '../../')
1481
1482 from Gnumed.pycommon import gmI18N
1483 gmI18N.activate_locale()
1484 gmI18N.install_domain()
1485
1486 #------------------------------------------------------------
1488 app = wx.PyWidgetTester(size = (400, 500))
1489 dlg = wx.MultiChoiceDialog (
1490 parent = None,
1491 message = 'test message',
1492 caption = 'test caption',
1493 choices = ['a', 'b', 'c', 'd', 'e']
1494 )
1495 dlg.ShowModal()
1496 sels = dlg.GetSelections()
1497 print "selected:"
1498 for sel in sels:
1499 print sel
1500 #------------------------------------------------------------
1506
1507 def refresh(lctrl):
1508 choices = ['a', 'b', 'c']
1509 lctrl.set_string_items(choices)
1510
1511 app = wx.PyWidgetTester(size = (200, 50))
1512 chosen = get_choices_from_list (
1513 # msg = 'select a health issue\nfrom the list below\n',
1514 caption = 'select health issues',
1515 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
1516 #columns = ['issue', 'no of episodes']
1517 columns = ['issue'],
1518 refresh_callback = refresh
1519 #, edit_callback = edit
1520 )
1521 print "chosen:"
1522 print chosen
1523 #------------------------------------------------------------
1525 app = wx.PyWidgetTester(size = (200, 50))
1526 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
1527 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
1528 #dlg.set_columns(['Plugins'], [])
1529 dlg.set_string_items(['patient', 'emr', 'docs'])
1530 result = dlg.ShowModal()
1531 print result
1532 print dlg.get_picks()
1533 #------------------------------------------------------------
1534 #test_get_choices_from_list()
1535 #test_wxMultiChoiceDialog()
1536 test_item_picker_dlg()
1537
1538 #================================================================
1539 #
1540
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jul 12 03:57:04 2013 | http://epydoc.sourceforge.net |