Package Gnumed :: Package wxpython :: Module gmListWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmListWidgets

  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  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmListWidgets.py,v $ 
 16  # $Id: gmListWidgets.py,v 1.36 2010/01/31 18:17:33 ncq Exp $ 
 17  __version__ = "$Revision: 1.36 $" 
 18  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
 19  __license__ = "GPL" 
 20   
 21   
 22  import sys, types 
 23   
 24   
 25  import wx 
 26  import wx.lib.mixins.listctrl as listmixins 
 27   
 28   
 29  if __name__ == '__main__': 
 30          sys.path.insert(0, '../../') 
 31  from Gnumed.business import gmPerson 
 32  from Gnumed.pycommon import gmTools, gmDispatcher 
 33  from Gnumed.wxpython import gmGuiHelpers 
 34  from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg, wxgGenericListManagerPnl 
 35   
 36  #================================================================ 
37 -def get_choices_from_list(parent=None, msg=None, caption=None, choices=None, selections=None, columns=None, data=None, edit_callback=None, new_callback=None, delete_callback=None, refresh_callback=None, single_selection=False, can_return_empty=False):
38 """Let user select item(s) from a list. 39 40 - edit_callback: (item data) 41 - new_callback: () 42 - delete_callback: (item data) 43 - refresh_callback: (listctrl) 44 45 returns None if cancelled 46 returns list (may be empty) of selected items 47 """ 48 if caption is None: 49 caption = _('generic multi choice dialog') 50 51 if single_selection: 52 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL) 53 else: 54 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg) 55 dlg.refresh_callback = refresh_callback 56 dlg.edit_callback = edit_callback 57 dlg.new_callback = new_callback 58 dlg.delete_callback = delete_callback 59 dlg.set_columns(columns = columns) 60 61 if refresh_callback is None: 62 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible 63 dlg.set_column_widths() 64 65 if data is not None: 66 dlg.set_data(data=data) # can override data set if refresh_callback is not None 67 68 if selections is not None: 69 dlg.set_selections(selections = selections) 70 dlg.can_return_empty = can_return_empty 71 72 btn_pressed = dlg.ShowModal() 73 sels = dlg.get_selected_item_data(only_one = single_selection) 74 dlg.Destroy() 75 76 if btn_pressed == wx.ID_OK: 77 if can_return_empty and (sels is None): 78 return [] 79 return sels 80 81 return None
82 #----------------------------------------------------------------
83 -class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
84 """A dialog holding a list and a few buttons to act on the items.""" 85
86 - def __init__(self, *args, **kwargs):
87 88 try: 89 msg = kwargs['msg'] 90 del kwargs['msg'] 91 except KeyError: msg = None 92 93 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs) 94 95 if msg is None: 96 self._LBL_message.Hide() 97 else: 98 self._LBL_message.SetLabel(msg) 99 100 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 101 self.new_callback = None # called when NEW button pressed, no argument passed in 102 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 103 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 104 105 self.can_return_empty = False
106 #------------------------------------------------------------
107 - def set_columns(self, columns=None):
108 self._LCTRL_items.set_columns(columns = columns)
109 #------------------------------------------------------------
110 - def set_column_widths(self, widths=None):
111 self._LCTRL_items.set_column_widths(widths = widths)
112 #------------------------------------------------------------
113 - def set_string_items(self, items = None):
114 self._LCTRL_items.set_string_items(items = items) 115 self._LCTRL_items.set_column_widths() 116 self._LCTRL_items.Select(0)
117 #------------------------------------------------------------
118 - def set_selections(self, selections = None):
119 self._LCTRL_items.set_selections(selections = selections)
120 #------------------------------------------------------------
121 - def set_data(self, data = None):
122 self._LCTRL_items.set_data(data = data)
123 #------------------------------------------------------------
124 - def get_selected_item_data(self, only_one=False):
125 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
126 #------------------------------------------------------------ 127 # event handlers 128 #------------------------------------------------------------
129 - def _on_list_item_selected(self, event):
130 self._BTN_ok.Enable(True) 131 self._BTN_ok.SetDefault() 132 if self.edit_callback is not None: 133 self._BTN_edit.Enable(True) 134 if self.delete_callback is not None: 135 self._BTN_delete.Enable(True)
136 #------------------------------------------------------------
137 - def _on_list_item_deselected(self, event):
138 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 139 if not self.can_return_empty: 140 self._BTN_ok.Enable(False) 141 self._BTN_cancel.SetDefault() 142 self._BTN_edit.Enable(False) 143 self._BTN_delete.Enable(False)
144 #------------------------------------------------------------
145 - def _on_new_button_pressed(self, event):
146 if not self.new_callback(): 147 return 148 if self.refresh_callback is None: 149 return 150 self.refresh_callback(lctrl = self._LCTRL_items) 151 self._LCTRL_items.set_column_widths()
152 #------------------------------------------------------------
153 - def _on_edit_button_pressed(self, event):
154 # if the edit button *can* be pressed there are *supposed* 155 # to be both an item selected and an editor configured 156 if not self.edit_callback(self._LCTRL_items.get_selected_item_data(only_one=True)): 157 return 158 if self.refresh_callback is None: 159 return 160 self.refresh_callback(lctrl = self._LCTRL_items) 161 self._LCTRL_items.set_column_widths()
162 #------------------------------------------------------------
163 - def _on_delete_button_pressed(self, event):
164 # if the delete button *can* be pressed there are *supposed* 165 # to be both an item selected and a deletor configured 166 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 167 if item_data is None: 168 return 169 if not self.delete_callback(item_data): 170 return 171 if self.refresh_callback is None: 172 return 173 self.refresh_callback(lctrl = self._LCTRL_items) 174 self._LCTRL_items.set_column_widths()
175 #------------------------------------------------------------ 176 # properties 177 #------------------------------------------------------------
178 - def _get_new_callback(self):
179 return self.__new_callback
180
181 - def _set_new_callback(self, callback):
182 if callback is not None: 183 if self.refresh_callback is None: 184 raise ValueError('refresh callback must be set before new callback can be set') 185 if not callable(callback): 186 raise ValueError('<new> callback is not a callable: %s' % callback) 187 self.__new_callback = callback 188 189 if callback is None: 190 self._BTN_new.Enable(False) 191 self._BTN_new.Hide() 192 else: 193 self._BTN_new.Enable(True) 194 self._BTN_new.Show()
195 196 new_callback = property(_get_new_callback, _set_new_callback) 197 #------------------------------------------------------------
198 - def _get_edit_callback(self):
199 return self.__edit_callback
200
201 - def _set_edit_callback(self, callback):
202 if callback is not None: 203 if self.refresh_callback is None: 204 raise ValueError('refresh callback must be set before edit callback can be set') 205 if not callable(callback): 206 raise ValueError('<edit> callback is not a callable: %s' % callback) 207 self.__edit_callback = callback 208 209 if callback is None: 210 self._BTN_edit.Enable(False) 211 self._BTN_edit.Hide() 212 else: 213 self._BTN_edit.Enable(True) 214 self._BTN_edit.Show()
215 216 edit_callback = property(_get_edit_callback, _set_edit_callback) 217 #------------------------------------------------------------
218 - def _get_delete_callback(self):
219 return self.__delete_callback
220
221 - def _set_delete_callback(self, callback):
222 if callback is not None: 223 if self.refresh_callback is None: 224 raise ValueError('refresh callback must be set before delete callback can be set') 225 if not callable(callback): 226 raise ValueError('<delete> callback is not a callable: %s' % callback) 227 self.__delete_callback = callback 228 229 if callback is None: 230 self._BTN_delete.Enable(False) 231 self._BTN_delete.Hide() 232 else: 233 self._BTN_delete.Enable(True) 234 self._BTN_delete.Show()
235 236 delete_callback = property(_get_delete_callback, _set_delete_callback) 237 #------------------------------------------------------------
238 - def _get_refresh_callback(self):
239 return self.__refresh_callback
240
242 self.refresh_callback(lctrl = self._LCTRL_items) 243 self._LCTRL_items.set_column_widths()
244
245 - def _set_refresh_callback(self, callback):
246 if callback is not None: 247 if not callable(callback): 248 raise ValueError('<refresh> callback is not a callable: %s' % callback) 249 self.__refresh_callback = callback 250 if callback is not None: 251 wx.CallAfter(self._set_refresh_callback_helper)
252 253 refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
254 #================================================================
255 -class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
256 """A panel holding a generic multi-column list and action buttions.""" 257
258 - def __init__(self, *args, **kwargs):
259 260 try: 261 msg = kwargs['msg'] 262 del kwargs['msg'] 263 except KeyError: msg = None 264 265 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs) 266 267 if msg is None: 268 self._LBL_message.Hide() 269 else: 270 self._LBL_message.SetLabel(msg) 271 272 # new/edit/delete must return True/False to enable refresh 273 self.__new_callback = None # called when NEW button pressed, no argument passed in 274 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 275 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 276 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled)
277 #------------------------------------------------------------ 278 # external API 279 #------------------------------------------------------------
280 - def set_columns(self, columns=None):
281 self._LCTRL_items.set_columns(columns = columns)
282 #------------------------------------------------------------
283 - def set_string_items(self, items = None):
284 self._LCTRL_items.set_string_items(items = items) 285 self._LCTRL_items.set_column_widths() 286 287 if (items is None) or (len(items) == 0): 288 self._BTN_edit.Enable(False) 289 self._BTN_remove.Enable(False) 290 else: 291 self._LCTRL_items.Select(0)
292 #------------------------------------------------------------
293 - def set_selections(self, selections = None):
294 self._LCTRL_items.set_selections(selections = selections)
295 #------------------------------------------------------------
296 - def set_data(self, data = None):
297 self._LCTRL_items.set_data(data = data)
298 #------------------------------------------------------------
299 - def get_selected_item_data(self, only_one=False):
300 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
301 #------------------------------------------------------------ 302 # event handlers 303 #------------------------------------------------------------
304 - def _on_list_item_selected(self, event):
305 if self.edit_callback is not None: 306 self._BTN_edit.Enable(True) 307 if self.delete_callback is not None: 308 self._BTN_remove.Enable(True)
309 #------------------------------------------------------------
310 - def _on_list_item_deselected(self, event):
311 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 312 self._BTN_edit.Enable(False) 313 self._BTN_remove.Enable(False)
314 #------------------------------------------------------------
315 - def _on_add_button_pressed(self, event):
316 if not self.new_callback(): 317 return 318 if self.refresh_callback is None: 319 return 320 self.refresh_callback(lctrl = self._LCTRL_items)
321 #------------------------------------------------------------
322 - def _on_edit_button_pressed(self, event):
323 item = self._LCTRL_items.get_selected_item_data(only_one=True) 324 if item is None: 325 return 326 if not self.edit_callback(item): 327 return 328 if self.refresh_callback is None: 329 return 330 self.refresh_callback(lctrl = self._LCTRL_items)
331 #------------------------------------------------------------
332 - def _on_remove_button_pressed(self, event):
333 item = self._LCTRL_items.get_selected_item_data(only_one=True) 334 if item is None: 335 return 336 if not self.delete_callback(item): 337 return 338 if self.refresh_callback is None: 339 return 340 self.refresh_callback(lctrl = self._LCTRL_items)
341 #------------------------------------------------------------ 342 # properties 343 #------------------------------------------------------------
344 - def _get_new_callback(self):
345 return self.__new_callback
346
347 - def _set_new_callback(self, callback):
348 self.__new_callback = callback 349 self._BTN_add.Enable(callback is not None)
350 351 new_callback = property(_get_new_callback, _set_new_callback)
352 #================================================================
353 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin):
354
355 - def __init__(self, *args, **kwargs):
356 357 try: 358 kwargs['style'] = kwargs['style'] | wx.LC_REPORT 359 except KeyError: 360 kwargs['style'] = wx.LC_REPORT 361 362 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL) 363 364 wx.ListCtrl.__init__(self, *args, **kwargs) 365 listmixins.ListCtrlAutoWidthMixin.__init__(self) 366 367 self.__widths = None 368 self.__data = None
369 #------------------------------------------------------------ 370 # setters 371 #------------------------------------------------------------
372 - def set_columns(self, columns=None):
373 """(Re)define the columns. 374 375 Note that this will (have to) delete the items. 376 """ 377 self.ClearAll() 378 if columns is None: 379 return 380 for idx in range(len(columns)): 381 self.InsertColumn(idx, columns[idx])
382 #------------------------------------------------------------
383 - def set_column_widths(self, widths=None):
384 """Set the column width policy. 385 386 widths = None: 387 use previous policy if any or default policy 388 widths != None: 389 use this policy and remember it for later calls 390 391 This means there is no way to *revert* to the default policy :-( 392 """ 393 # explicit policy ? 394 if widths is not None: 395 self.__widths = widths 396 for idx in range(len(self.__widths)): 397 self.SetColumnWidth(col = idx, width = self.__widths[idx]) 398 return 399 400 # previous policy ? 401 if self.__widths is not None: 402 for idx in range(len(self.__widths)): 403 self.SetColumnWidth(col = idx, width = self.__widths[idx]) 404 return 405 406 # default policy ! 407 if self.GetItemCount() == 0: 408 width_type = wx.LIST_AUTOSIZE_USEHEADER 409 else: 410 width_type = wx.LIST_AUTOSIZE 411 for idx in range(self.GetColumnCount()): 412 self.SetColumnWidth(col = idx, width = width_type)
413 #------------------------------------------------------------
414 - def set_string_items(self, items = None):
415 """All item members must be unicode()able or None.""" 416 417 self.DeleteAllItems() 418 self.__data = items 419 420 if items is None: 421 return 422 423 for item in items: 424 try: 425 item[0] 426 if not isinstance(item, basestring): 427 is_numerically_iterable = True 428 else: 429 is_numerically_iterable = False 430 except TypeError: 431 is_numerically_iterable = False 432 433 if is_numerically_iterable: 434 # cannot use errors='replace' since then 435 # None/ints/unicode strings fail to get encoded 436 col_val = unicode(item[0]) 437 row_num = self.InsertStringItem(index = sys.maxint, label = col_val) 438 for col_idx in range(1, min(self.GetColumnCount(), len(item))): 439 col_val = unicode(item[col_idx]) 440 self.SetStringItem(index = row_num, col = col_idx, label = col_val) 441 else: 442 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded 443 col_val = unicode(item) 444 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
445 #------------------------------------------------------------
446 - def set_data(self, data = None):
447 """<data must be a list corresponding to the item indices>""" 448 self.__data = data
449 #------------------------------------------------------------
450 - def set_selections(self, selections=None):
451 self.Select(0, on = 0) 452 for idx in selections: 453 self.Select(idx = idx, on = 1)
454 #------------------------------------------------------------ 455 # getters 456 #------------------------------------------------------------
457 - def get_column_labels(self):
458 labels = [] 459 for col_idx in self.GetColumnCount(): 460 col = self.GetColumn(col = col_idx) 461 labels.append(col.GetText()) 462 return labels
463 #------------------------------------------------------------
464 - def get_item_data(self, item_idx = None):
465 if self.__data is None: # this isn't entirely clean 466 return None 467 468 return self.__data[item_idx]
469 #------------------------------------------------------------
470 - def get_selected_items(self, only_one=False):
471 472 if self.__is_single_selection or only_one: 473 return self.GetFirstSelected() 474 475 items = [] 476 idx = self.GetFirstSelected() 477 while idx != -1: 478 items.append(idx) 479 idx = self.GetNextSelected(idx) 480 481 return items
482 #------------------------------------------------------------
483 - def get_selected_item_data(self, only_one=False):
484 485 if self.__is_single_selection or only_one: 486 if self.__data is None: 487 return None 488 idx = self.GetFirstSelected() 489 if idx == -1: 490 return None 491 return self.__data[idx] 492 493 data = [] 494 if self.__data is None: 495 return data 496 idx = self.GetFirstSelected() 497 while idx != -1: 498 data.append(self.__data[idx]) 499 idx = self.GetNextSelected(idx) 500 501 return data
502 #------------------------------------------------------------
503 - def deselect_selected_item(self):
504 self.Select(idx = self.GetFirstSelected(), on = 0)
505 #================================================================ 506 # main 507 #---------------------------------------------------------------- 508 if __name__ == '__main__': 509 510 from Gnumed.pycommon import gmI18N 511 gmI18N.activate_locale() 512 gmI18N.install_domain() 513 514 #------------------------------------------------------------
515 - def test_wxMultiChoiceDialog():
516 app = wx.PyWidgetTester(size = (400, 500)) 517 dlg = wx.MultiChoiceDialog ( 518 parent = None, 519 message = 'test message', 520 caption = 'test caption', 521 choices = ['a', 'b', 'c', 'd', 'e'] 522 ) 523 dlg.ShowModal() 524 sels = dlg.GetSelections() 525 print "selected:" 526 for sel in sels: 527 print sel
528 #------------------------------------------------------------
529 - def test_get_choices_from_list():
530 531 def edit(argument): 532 print "editor called with:" 533 print argument
534 535 def refresh(lctrl): 536 choices = ['a', 'b', 'c'] 537 lctrl.set_string_items(choices) 538 539 app = wx.PyWidgetTester(size = (200, 50)) 540 chosen = get_choices_from_list ( 541 # msg = 'select a health issue\nfrom the list below\n', 542 caption = 'select health issues', 543 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']], 544 #columns = ['issue', 'no of episodes'] 545 columns = ['issue'], 546 refresh_callback = refresh 547 #, edit_callback = edit 548 ) 549 print "chosen:" 550 print chosen 551 #------------------------------------------------------------ 552 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 553 test_get_choices_from_list() 554 #test_wxMultiChoiceDialog() 555 556 #================================================================ 557 # $Log: gmListWidgets.py,v $ 558 # Revision 1.36 2010/01/31 18:17:33 ncq 559 # - make refresh callback setting smarter: set column widths after setting items 560 # 561 # Revision 1.35 2010/01/21 08:43:23 ncq 562 # - somewhat support setting col widths within get-choice-from-list 563 # 564 # Revision 1.34 2009/11/28 18:30:07 ncq 565 # - hide disabled buttons/show enabled ones 566 # 567 # Revision 1.33 2009/06/29 15:07:58 ncq 568 # - disable edit/delete buttons when setting items to None or [] 569 # 570 # Revision 1.32 2009/06/20 12:45:22 ncq 571 # - call refresh from refresh property setter if callable and not None 572 # 573 # Revision 1.31 2009/06/11 12:37:25 ncq 574 # - much simplified initial setup of list ctrls 575 # 576 # Revision 1.30 2009/06/04 16:32:01 ncq 577 # - use refresh from init if available to simplify external setup code 578 # 579 # Revision 1.29 2009/04/16 12:49:47 ncq 580 # - more sanity checks regarding action callbacks 581 # 582 # Revision 1.28 2009/01/17 23:07:29 ncq 583 # - support remembering previous widths policy 584 # 585 # Revision 1.27 2009/01/15 11:39:59 ncq 586 # - cleanup 587 # 588 # Revision 1.26 2008/12/25 16:55:36 ncq 589 # - allow returniny empty list = no item selected if desired 590 # 591 # Revision 1.25 2008/08/06 13:22:14 ncq 592 # - fix detection of item list type 593 # 594 # Revision 1.24 2008/07/24 14:00:18 ncq 595 # - better comments 596 # - resize columns after list refreshing in generic list selector 597 # - differentiate between iterables and non-iterables by means of 598 # an exception rather than checking for type.ListType in set_string_items 599 # 600 # Revision 1.23 2008/05/31 16:33:07 ncq 601 # - add TODO with URL 602 # 603 # Revision 1.22 2008/02/26 16:28:04 ncq 604 # - when auto-setting col widths in lists w/o items use header as width ;-) 605 # 606 # Revision 1.21 2007/11/28 22:37:00 ncq 607 # - robustify in the absence of selected values 608 # 609 # Revision 1.20 2007/11/23 23:34:39 ncq 610 # - when explicitely setting the selections deselect the 611 # 0th-index item selected by default 612 # 613 # Revision 1.19 2007/11/17 16:38:13 ncq 614 # - cGenericListManagerPnl 615 # 616 # Revision 1.18 2007/10/08 12:56:02 ncq 617 # - document callbacks 618 # - protect against self.__data being None in get(_selected)_item_data() 619 # 620 # Revision 1.17 2007/09/24 18:37:08 ncq 621 # - get_column_labels() 622 # 623 # Revision 1.16 2007/09/20 19:10:15 ncq 624 # - carefully handle list item insertion - handle both list 625 # of lists and list of strings 626 # 627 # Revision 1.15 2007/09/07 22:38:04 ncq 628 # - remove Fit() call since it's counterproductive for the list 629 # 630 # Revision 1.14 2007/09/02 20:54:26 ncq 631 # - remove cruft 632 # - support refresh_callback 633 # 634 # Revision 1.13 2007/08/31 23:05:05 ncq 635 # - fix single selection list 636 # 637 # Revision 1.12 2007/08/29 14:41:54 ncq 638 # - no more singular get_choice_from_list() 639 # - support add/delete callbacks in generic list selector 640 # 641 # Revision 1.11 2007/08/20 16:22:51 ncq 642 # - make get_choice(s)_from_list() more generic 643 # - cleanup, improved test 644 # - support edit button and message in generic list selector 645 # 646 # Revision 1.10 2007/07/22 09:26:25 ncq 647 # - new get_choice_from_list() 648 # 649 # Revision 1.9 2007/07/09 12:45:47 ncq 650 # - fix unicode()ing in set_string_items(): can't use (..., errors='replace') :-( 651 # - factor out cPatientListingCtrl into gmDataMiningWidgets.py 652 # 653 # Revision 1.8 2007/07/07 12:42:00 ncq 654 # - set_string_items now applies unicode() to all item members 655 # - cPatientListingCtrl and test suite 656 # 657 # Revision 1.7 2007/06/28 12:38:15 ncq 658 # - fix logic reversal in get_selected_*() 659 # 660 # Revision 1.6 2007/06/18 20:33:56 ncq 661 # - add get_choice(s)_from_list() 662 # - add cGenericListSelectorDlg 663 # - add set_string_items()/set_selections()/get_selected_items() 664 # - improve test suite 665 # 666 # Revision 1.5 2007/06/12 16:03:02 ncq 667 # - properly get rid of all columns in set_columns() 668 # 669 # Revision 1.4 2007/04/09 18:51:47 ncq 670 # - add support for multiple selections and auto-setting the widths 671 # 672 # Revision 1.3 2007/03/18 14:09:31 ncq 673 # - add set_columns() and set_column_widths() 674 # 675 # Revision 1.2 2006/12/11 20:50:45 ncq 676 # - get_selected_item_data() 677 # - deselect_selected_item() 678 # 679 # Revision 1.1 2006/07/23 20:34:50 ncq 680 # - list controls and widgets 681 # 682 # 683