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