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  __version__ = "$Revision: 1.37 $" 
  16  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
  17  __license__ = "GPL" 
  18   
  19   
  20  import sys, types 
  21   
  22   
  23  import wx 
  24  import wx.lib.mixins.listctrl as listmixins 
  25   
  26   
  27  if __name__ == '__main__': 
  28          sys.path.insert(0, '../../') 
  29   
  30  #================================================================ 
  31  # FIXME: configurable callback on double-click action 
  32   
33 -def get_choices_from_list ( 34 parent=None, 35 msg=None, 36 caption=None, 37 choices=None, 38 selections=None, 39 columns=None, 40 data=None, 41 edit_callback=None, 42 new_callback=None, 43 delete_callback=None, 44 refresh_callback=None, 45 single_selection=False, 46 can_return_empty=False, 47 ignore_OK_button=False, 48 left_extra_button=None, 49 middle_extra_button=None, 50 right_extra_button=None, 51 list_tooltip_callback=None):
52 """Let user select item(s) from a list. 53 54 - new_callback: () 55 - edit_callback: (item data) 56 - delete_callback: (item data) 57 - refresh_callback: (listctrl) 58 - list_tooltip_callback: (item data) 59 60 - left/middle/right_extra_button: (label, tooltip, <callback>) 61 <callback> is called with item_data as the only argument 62 63 returns: 64 on [CANCEL]: None 65 on [OK]: 66 if any items selected: 67 list of selected items 68 else: 69 if can_return_empty is True: 70 empty list 71 else: 72 None 73 """ 74 if caption is None: 75 caption = _('generic multi choice dialog') 76 77 if single_selection: 78 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL) 79 else: 80 dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg) 81 82 dlg.refresh_callback = refresh_callback 83 dlg.edit_callback = edit_callback 84 dlg.new_callback = new_callback 85 dlg.delete_callback = delete_callback 86 dlg.list_tooltip_callback = list_tooltip_callback 87 88 dlg.ignore_OK_button = ignore_OK_button 89 dlg.left_extra_button = left_extra_button 90 dlg.middle_extra_button = middle_extra_button 91 dlg.right_extra_button = right_extra_button 92 93 dlg.set_columns(columns = columns) 94 95 if refresh_callback is None: 96 dlg.set_string_items(items = choices) # list ctrl will refresh anyway if possible 97 dlg.set_column_widths() 98 99 if data is not None: 100 dlg.set_data(data = data) # can override data set if refresh_callback is not None 101 102 if selections is not None: 103 dlg.set_selections(selections = selections) 104 dlg.can_return_empty = can_return_empty 105 106 btn_pressed = dlg.ShowModal() 107 sels = dlg.get_selected_item_data(only_one = single_selection) 108 dlg.Destroy() 109 110 if btn_pressed == wx.ID_OK: 111 if can_return_empty and (sels is None): 112 return [] 113 return sels 114 115 return None
116 #---------------------------------------------------------------- 117 from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg 118
119 -class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
120 """A dialog holding a list and a few buttons to act on the items.""" 121 122 # FIXME: configurable callback on double-click action 123
124 - def __init__(self, *args, **kwargs):
125 126 try: 127 msg = kwargs['msg'] 128 del kwargs['msg'] 129 except KeyError: msg = None 130 131 wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs) 132 133 self.message = msg 134 135 self.left_extra_button = None 136 self.middle_extra_button = None 137 self.right_extra_button = None 138 139 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 140 self.new_callback = None # called when NEW button pressed, no argument passed in 141 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 142 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 143 144 self.can_return_empty = False 145 self.ignore_OK_button = False # by default do show/use the OK button
146 #------------------------------------------------------------
147 - def set_columns(self, columns=None):
148 self._LCTRL_items.set_columns(columns = columns)
149 #------------------------------------------------------------
150 - def set_column_widths(self, widths=None):
151 self._LCTRL_items.set_column_widths(widths = widths)
152 #------------------------------------------------------------
153 - def set_string_items(self, items = None):
154 self._LCTRL_items.set_string_items(items = items) 155 self._LCTRL_items.set_column_widths() 156 self._LCTRL_items.Select(0)
157 #------------------------------------------------------------
158 - def set_selections(self, selections = None):
159 self._LCTRL_items.set_selections(selections = selections) 160 if selections is None: 161 return 162 if len(selections) == 0: 163 return 164 if self.ignore_OK_button: 165 return 166 self._BTN_ok.Enable(True) 167 self._BTN_ok.SetDefault()
168 #------------------------------------------------------------
169 - def set_data(self, data = None):
170 self._LCTRL_items.set_data(data = data)
171 #------------------------------------------------------------
172 - def get_selected_item_data(self, only_one=False):
173 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
174 #------------------------------------------------------------ 175 # event handlers 176 #------------------------------------------------------------
177 - def _on_list_item_selected(self, event):
178 if not self.__ignore_OK_button: 179 self._BTN_ok.SetDefault() 180 self._BTN_ok.Enable(True) 181 182 if self.edit_callback is not None: 183 self._BTN_edit.Enable(True) 184 185 if self.delete_callback is not None: 186 self._BTN_delete.Enable(True)
187 #------------------------------------------------------------
188 - def _on_list_item_deselected(self, event):
189 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 190 if not self.can_return_empty: 191 self._BTN_cancel.SetDefault() 192 self._BTN_ok.Enable(False) 193 self._BTN_edit.Enable(False) 194 self._BTN_delete.Enable(False)
195 #------------------------------------------------------------
196 - def _on_new_button_pressed(self, event):
197 if not self.new_callback(): 198 self._LCTRL_items.SetFocus() 199 return 200 if self.refresh_callback is None: 201 self._LCTRL_items.SetFocus() 202 return 203 wx.BeginBusyCursor() 204 try: 205 self.refresh_callback(lctrl = self._LCTRL_items) 206 finally: 207 wx.EndBusyCursor() 208 self._LCTRL_items.set_column_widths() 209 self._LCTRL_items.SetFocus()
210 #------------------------------------------------------------
211 - def _on_edit_button_pressed(self, event):
212 # if the edit button *can* be pressed there are *supposed* 213 # to be both an item selected and an editor configured 214 if not self.edit_callback(self._LCTRL_items.get_selected_item_data(only_one=True)): 215 self._LCTRL_items.SetFocus() 216 return 217 if self.refresh_callback is None: 218 self._LCTRL_items.SetFocus() 219 return 220 wx.BeginBusyCursor() 221 try: 222 self.refresh_callback(lctrl = self._LCTRL_items) 223 finally: 224 wx.EndBusyCursor() 225 self._LCTRL_items.set_column_widths() 226 self._LCTRL_items.SetFocus()
227 #------------------------------------------------------------
228 - def _on_delete_button_pressed(self, event):
229 # if the delete button *can* be pressed there are *supposed* 230 # to be both an item selected and a deletor configured 231 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 232 if item_data is None: 233 self._LCTRL_items.SetFocus() 234 return 235 if not self.delete_callback(item_data): 236 self._LCTRL_items.SetFocus() 237 return 238 if self.refresh_callback is None: 239 self._LCTRL_items.SetFocus() 240 return 241 wx.BeginBusyCursor() 242 try: 243 self.refresh_callback(lctrl = self._LCTRL_items) 244 finally: 245 wx.EndBusyCursor() 246 self._LCTRL_items.set_column_widths() 247 self._LCTRL_items.SetFocus()
248 #------------------------------------------------------------
249 - def _on_left_extra_button_pressed(self, event):
250 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 251 if not self.__left_extra_button_callback(item_data): 252 self._LCTRL_items.SetFocus() 253 return 254 if self.refresh_callback is None: 255 self._LCTRL_items.SetFocus() 256 return 257 wx.BeginBusyCursor() 258 try: 259 self.refresh_callback(lctrl = self._LCTRL_items) 260 finally: 261 wx.EndBusyCursor() 262 self._LCTRL_items.set_column_widths() 263 self._LCTRL_items.SetFocus()
264 #------------------------------------------------------------
265 - def _on_middle_extra_button_pressed(self, event):
266 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 267 if not self.__middle_extra_button_callback(item_data): 268 self._LCTRL_items.SetFocus() 269 return 270 if self.refresh_callback is None: 271 self._LCTRL_items.SetFocus() 272 return 273 wx.BeginBusyCursor() 274 try: 275 self.refresh_callback(lctrl = self._LCTRL_items) 276 finally: 277 wx.EndBusyCursor() 278 self._LCTRL_items.set_column_widths() 279 self._LCTRL_items.SetFocus()
280 #------------------------------------------------------------
281 - def _on_right_extra_button_pressed(self, event):
282 item_data = self._LCTRL_items.get_selected_item_data(only_one=True) 283 if not self.__right_extra_button_callback(item_data): 284 self._LCTRL_items.SetFocus() 285 return 286 if self.refresh_callback is None: 287 self._LCTRL_items.SetFocus() 288 return 289 wx.BeginBusyCursor() 290 try: 291 self.refresh_callback(lctrl = self._LCTRL_items) 292 finally: 293 wx.EndBusyCursor() 294 self._LCTRL_items.set_column_widths() 295 self._LCTRL_items.SetFocus()
296 #------------------------------------------------------------ 297 # properties 298 #------------------------------------------------------------
299 - def _set_ignore_OK_button(self, ignored):
300 self.__ignore_OK_button = ignored 301 if self.__ignore_OK_button: 302 self._BTN_ok.Hide() 303 self._BTN_ok.Enable(False) 304 else: 305 self._BTN_ok.Show() 306 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 307 if self.can_return_empty: 308 self._BTN_ok.Enable(True) 309 else: 310 self._BTN_ok.Enable(False) 311 self._BTN_cancel.SetDefault()
312 313 ignore_OK_button = property(lambda x:x, _set_ignore_OK_button) 314 #------------------------------------------------------------
315 - def _set_left_extra_button(self, definition):
316 if definition is None: 317 self._BTN_extra_left.Enable(False) 318 self._BTN_extra_left.Hide() 319 self.__left_extra_button_callback = None 320 return 321 322 (label, tooltip, callback) = definition 323 if not callable(callback): 324 raise ValueError('<left extra button> callback is not a callable: %s' % callback) 325 self.__left_extra_button_callback = callback 326 self._BTN_extra_left.SetLabel(label) 327 self._BTN_extra_left.SetToolTipString(tooltip) 328 self._BTN_extra_left.Enable(True) 329 self._BTN_extra_left.Show()
330 331 left_extra_button = property(lambda x:x, _set_left_extra_button) 332 #------------------------------------------------------------
333 - def _set_middle_extra_button(self, definition):
334 if definition is None: 335 self._BTN_extra_middle.Enable(False) 336 self._BTN_extra_middle.Hide() 337 self.__middle_extra_button_callback = None 338 return 339 340 (label, tooltip, callback) = definition 341 if not callable(callback): 342 raise ValueError('<middle extra button> callback is not a callable: %s' % callback) 343 self.__middle_extra_button_callback = callback 344 self._BTN_extra_middle.SetLabel(label) 345 self._BTN_extra_middle.SetToolTipString(tooltip) 346 self._BTN_extra_middle.Enable(True) 347 self._BTN_extra_middle.Show()
348 349 middle_extra_button = property(lambda x:x, _set_middle_extra_button) 350 #------------------------------------------------------------
351 - def _set_right_extra_button(self, definition):
352 if definition is None: 353 self._BTN_extra_right.Enable(False) 354 self._BTN_extra_right.Hide() 355 self.__right_extra_button_callback = None 356 return 357 358 (label, tooltip, callback) = definition 359 if not callable(callback): 360 raise ValueError('<right extra button> callback is not a callable: %s' % callback) 361 self.__right_extra_button_callback = callback 362 self._BTN_extra_right.SetLabel(label) 363 self._BTN_extra_right.SetToolTipString(tooltip) 364 self._BTN_extra_right.Enable(True) 365 self._BTN_extra_right.Show()
366 367 right_extra_button = property(lambda x:x, _set_right_extra_button) 368 #------------------------------------------------------------
369 - def _get_new_callback(self):
370 return self.__new_callback
371
372 - def _set_new_callback(self, callback):
373 if callback is not None: 374 if self.refresh_callback is None: 375 raise ValueError('refresh callback must be set before new callback can be set') 376 if not callable(callback): 377 raise ValueError('<new> callback is not a callable: %s' % callback) 378 self.__new_callback = callback 379 380 if callback is None: 381 self._BTN_new.Enable(False) 382 self._BTN_new.Hide() 383 else: 384 self._BTN_new.Enable(True) 385 self._BTN_new.Show()
386 387 new_callback = property(_get_new_callback, _set_new_callback) 388 #------------------------------------------------------------
389 - def _get_edit_callback(self):
390 return self.__edit_callback
391
392 - def _set_edit_callback(self, callback):
393 if callback is not None: 394 if not callable(callback): 395 raise ValueError('<edit> callback is not a callable: %s' % callback) 396 self.__edit_callback = callback 397 398 if callback is None: 399 self._BTN_edit.Enable(False) 400 self._BTN_edit.Hide() 401 else: 402 self._BTN_edit.Enable(True) 403 self._BTN_edit.Show()
404 405 edit_callback = property(_get_edit_callback, _set_edit_callback) 406 #------------------------------------------------------------
407 - def _get_delete_callback(self):
408 return self.__delete_callback
409
410 - def _set_delete_callback(self, callback):
411 if callback is not None: 412 if self.refresh_callback is None: 413 raise ValueError('refresh callback must be set before delete callback can be set') 414 if not callable(callback): 415 raise ValueError('<delete> callback is not a callable: %s' % callback) 416 self.__delete_callback = callback 417 418 if callback is None: 419 self._BTN_delete.Enable(False) 420 self._BTN_delete.Hide() 421 else: 422 self._BTN_delete.Enable(True) 423 self._BTN_delete.Show()
424 425 delete_callback = property(_get_delete_callback, _set_delete_callback) 426 #------------------------------------------------------------
427 - def _get_refresh_callback(self):
428 return self.__refresh_callback
429
431 wx.BeginBusyCursor() 432 try: 433 self.refresh_callback(lctrl = self._LCTRL_items) 434 finally: 435 wx.EndBusyCursor() 436 self._LCTRL_items.set_column_widths()
437
438 - def _set_refresh_callback(self, callback):
439 if callback is not None: 440 if not callable(callback): 441 raise ValueError('<refresh> callback is not a callable: %s' % callback) 442 self.__refresh_callback = callback 443 if callback is not None: 444 wx.CallAfter(self._set_refresh_callback_helper)
445 446 refresh_callback = property(_get_refresh_callback, _set_refresh_callback) 447 #------------------------------------------------------------
448 - def _set_list_tooltip_callback(self, callback):
449 self._LCTRL_items.item_tooltip_callback = callback
450 451 list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback) 452 #def _get_tooltip(self, item): # inside a class 453 #def _get_tooltip(item): # outside a class 454 #------------------------------------------------------------
455 - def _set_message(self, message):
456 if message is None: 457 self._LBL_message.Hide() 458 return 459 self._LBL_message.SetLabel(message) 460 self._LBL_message.Show()
461 462 message = property(lambda x:x, _set_message)
463 #================================================================ 464 from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl 465
466 -class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
467 """A panel holding a generic multi-column list and action buttions.""" 468
469 - def __init__(self, *args, **kwargs):
470 471 try: 472 msg = kwargs['msg'] 473 del kwargs['msg'] 474 except KeyError: msg = None 475 476 wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs) 477 478 if msg is None: 479 self._LBL_message.Hide() 480 else: 481 self._LBL_message.SetLabel(msg) 482 483 # new/edit/delete must return True/False to enable refresh 484 self.__new_callback = None # called when NEW button pressed, no argument passed in 485 self.edit_callback = None # called when EDIT button pressed, data of topmost selected item passed in 486 self.delete_callback = None # called when DELETE button pressed, data of topmost selected item passed in 487 self.refresh_callback = None # called when new/edit/delete callbacks return True (IOW were not cancelled) 488 489 self.__select_callback = None # called when an item is selected, data of topmost selected item passed in
490 #------------------------------------------------------------ 491 # external API 492 #------------------------------------------------------------
493 - def set_columns(self, columns=None):
494 self._LCTRL_items.set_columns(columns = columns)
495 #------------------------------------------------------------
496 - def set_string_items(self, items = None):
497 self._LCTRL_items.set_string_items(items = items) 498 self._LCTRL_items.set_column_widths() 499 500 if (items is None) or (len(items) == 0): 501 self._BTN_edit.Enable(False) 502 self._BTN_remove.Enable(False) 503 else: 504 self._LCTRL_items.Select(0)
505 #------------------------------------------------------------
506 - def set_selections(self, selections = None):
507 self._LCTRL_items.set_selections(selections = selections)
508 #------------------------------------------------------------
509 - def set_data(self, data = None):
510 self._LCTRL_items.set_data(data = data)
511 #------------------------------------------------------------
512 - def get_selected_item_data(self, only_one=False):
513 return self._LCTRL_items.get_selected_item_data(only_one=only_one)
514 #------------------------------------------------------------ 515 # event handlers 516 #------------------------------------------------------------
517 - def _on_list_item_selected(self, event):
518 if self.edit_callback is not None: 519 self._BTN_edit.Enable(True) 520 if self.delete_callback is not None: 521 self._BTN_remove.Enable(True) 522 if self.__select_callback is not None: 523 item = self._LCTRL_items.get_selected_item_data(only_one=True) 524 self.__select_callback(item)
525 #------------------------------------------------------------
526 - def _on_list_item_deselected(self, event):
527 if self._LCTRL_items.get_selected_items(only_one=True) == -1: 528 self._BTN_edit.Enable(False) 529 self._BTN_remove.Enable(False) 530 if self.__select_callback is not None: 531 self.__select_callback(None)
532 #------------------------------------------------------------
533 - def _on_add_button_pressed(self, event):
534 if not self.new_callback(): 535 return 536 if self.refresh_callback is None: 537 return 538 wx.BeginBusyCursor() 539 try: 540 self.refresh_callback(lctrl = self._LCTRL_items) 541 finally: 542 wx.EndBusyCursor()
543 #------------------------------------------------------------
544 - def _on_list_item_activated(self, event):
545 if self.edit_callback is None: 546 return 547 self._on_edit_button_pressed(event)
548 #------------------------------------------------------------
549 - def _on_edit_button_pressed(self, event):
550 item = self._LCTRL_items.get_selected_item_data(only_one=True) 551 if item is None: 552 return 553 if not self.edit_callback(item): 554 return 555 if self.refresh_callback is None: 556 return 557 wx.BeginBusyCursor() 558 try: 559 self.refresh_callback(lctrl = self._LCTRL_items) 560 finally: 561 wx.EndBusyCursor()
562 #------------------------------------------------------------
563 - def _on_remove_button_pressed(self, event):
564 item = self._LCTRL_items.get_selected_item_data(only_one=True) 565 if item is None: 566 return 567 if not self.delete_callback(item): 568 return 569 if self.refresh_callback is None: 570 return 571 wx.BeginBusyCursor() 572 try: 573 self.refresh_callback(lctrl = self._LCTRL_items) 574 finally: 575 wx.EndBusyCursor()
576 #------------------------------------------------------------ 577 # properties 578 #------------------------------------------------------------
579 - def _get_new_callback(self):
580 return self.__new_callback
581
582 - def _set_new_callback(self, callback):
583 if callback is not None: 584 if not callable(callback): 585 raise ValueError('<new> callback is not a callable: %s' % callback) 586 self.__new_callback = callback 587 self._BTN_add.Enable(callback is not None)
588 589 new_callback = property(_get_new_callback, _set_new_callback) 590 #------------------------------------------------------------
591 - def _get_select_callback(self):
592 return self.__select_callback
593
594 - def _set_select_callback(self, callback):
595 if callback is not None: 596 if not callable(callback): 597 raise ValueError('<select> callback is not a callable: %s' % callback) 598 self.__select_callback = callback
599 600 select_callback = property(_get_select_callback, _set_select_callback) 601 #------------------------------------------------------------
602 - def _get_message(self):
603 return self._LBL_message.GetLabel()
604
605 - def _set_message(self, msg):
606 if msg is None: 607 self._LBL_message.Hide() 608 self._LBL_message.SetLabel(u'') 609 else: 610 self._LBL_message.SetLabel(msg) 611 self._LBL_message.Show() 612 self.Layout()
613 614 message = property(_get_message, _set_message)
615 #================================================================ 616 from Gnumed.wxGladeWidgets import wxgItemPickerDlg 617
618 -class cItemPickerDlg(wxgItemPickerDlg.wxgItemPickerDlg):
619
620 - def __init__(self, *args, **kwargs):
621 622 try: 623 msg = kwargs['msg'] 624 del kwargs['msg'] 625 except KeyError: 626 msg = None 627 628 wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs) 629 630 if msg is None: 631 self._LBL_msg.Hide() 632 else: 633 self._LBL_msg.SetLabel(msg) 634 635 self._LCTRL_left.activate_callback = self.__pick_selected 636 #self._LCTRL_left.item_tooltip_callback = self.__on_get_item_tooltip 637 638 self._LCTRL_left.SetFocus()
639 #------------------------------------------------------------ 640 # external API 641 #------------------------------------------------------------
642 - def set_columns(self, columns=None, columns_right=None):
643 self._LCTRL_left.set_columns(columns = columns) 644 if columns_right is None: 645 self._LCTRL_right.set_columns(columns = columns) 646 else: 647 if len(columns_right) < len(columns): 648 cols = columns 649 else: 650 cols = columns_right[:len(columns)] 651 self._LCTRL_right.set_columns(columns = cols)
652 #------------------------------------------------------------
653 - def set_string_items(self, items = None):
654 self._LCTRL_left.set_string_items(items = items) 655 self._LCTRL_left.set_column_widths() 656 self._LCTRL_right.set_string_items() 657 658 self._BTN_left2right.Enable(False) 659 self._BTN_right2left.Enable(False)
660 #------------------------------------------------------------
661 - def set_selections(self, selections = None):
662 self._LCTRL_left.set_selections(selections = selections)
663 #------------------------------------------------------------
664 - def set_choices(self, choices=None, data=None):
665 self.set_string_items(items = choices) 666 if data is not None: 667 self.set_data(data = data)
668 #------------------------------------------------------------
669 - def set_picks(self, picks=None, data=None):
670 self._LCTRL_right.set_string_items(picks) 671 self._LCTRL_right.set_column_widths() 672 if data is not None: 673 self._LCTRL_right.set_data(data = data)
674 #------------------------------------------------------------
675 - def set_data(self, data = None):
676 self._LCTRL_left.set_data(data = data)
677 #------------------------------------------------------------
678 - def get_picks(self):
679 return self._LCTRL_right.get_item_data()
680 681 picks = property(get_picks, lambda x:x) 682 #------------------------------------------------------------ 683 # internal helpers 684 #------------------------------------------------------------
685 - def __pick_selected(self, event=None):
686 if self._LCTRL_left.get_selected_items(only_one = True) == -1: 687 return 688 689 right_items = self._LCTRL_right.get_string_items() 690 right_data = self._LCTRL_right.get_item_data() 691 692 right_items.extend(self._LCTRL_left.get_selected_string_items(only_one = False)) 693 self._LCTRL_right.set_string_items(items = right_items) 694 del right_items 695 696 if right_data is None: 697 self._LCTRL_right.set_data(data = self._LCTRL_left.get_selected_item_data(only_one = False)) 698 else: 699 right_data.extend(self._LCTRL_left.get_selected_item_data(only_one = False)) 700 self._LCTRL_right.set_data(data = right_data) 701 del right_data 702 703 self._LCTRL_right.set_column_widths()
704 #------------------------------------------------------------
705 - def __remove_selected_picks(self):
706 if self._LCTRL_right.get_selected_items(only_one = True) == -1: 707 return 708 709 for item_idx in self._LCTRL_right.get_selected_items(only_one = False): 710 self._LCTRL_right.remove_item(item_idx) 711 712 if self._LCTRL_right.GetItemCount() == 0: 713 self._BTN_right2left.Enable(False)
714 #------------------------------------------------------------ 715 # event handlers 716 #------------------------------------------------------------
717 - def _on_left_list_item_selected(self, event):
718 self._BTN_left2right.Enable(True)
719 #------------------------------------------------------------
720 - def _on_left_list_item_deselected(self, event):
721 if self._LCTRL_left.get_selected_items(only_one = True) == -1: 722 self._BTN_left2right.Enable(False)
723 #------------------------------------------------------------
724 - def _on_right_list_item_selected(self, event):
725 self._BTN_right2left.Enable(True)
726 #------------------------------------------------------------
727 - def _on_right_list_item_deselected(self, event):
728 if self._LCTRL_right.get_selected_items(only_one = True) == -1: 729 self._BTN_right2left.Enable(False)
730 #------------------------------------------------------------
731 - def _on_button_left2right_pressed(self, event):
732 self.__pick_selected()
733 #------------------------------------------------------------
734 - def _on_button_right2left_pressed(self, event):
735 self.__remove_selected_picks()
736 #================================================================
737 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin):
738 739 # FIXME: searching by typing 740
741 - def __init__(self, *args, **kwargs):
742 743 try: 744 kwargs['style'] = kwargs['style'] | wx.LC_REPORT 745 except KeyError: 746 kwargs['style'] = wx.LC_REPORT 747 748 self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL) 749 750 wx.ListCtrl.__init__(self, *args, **kwargs) 751 listmixins.ListCtrlAutoWidthMixin.__init__(self) 752 753 self.__widths = None 754 self.__data = None 755 self.__activate_callback = None 756 757 self.Bind(wx.EVT_MOTION, self._on_mouse_motion) 758 self.__item_tooltip_callback = None 759 self.__tt_last_item = None 760 self.__tt_static_part = _("""Select the items you want to work on. 761 762 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.""")
763 #------------------------------------------------------------ 764 # setters 765 #------------------------------------------------------------
766 - def set_columns(self, columns=None):
767 """(Re)define the columns. 768 769 Note that this will (have to) delete the items. 770 """ 771 self.ClearAll() 772 self.__tt_last_item = None 773 if columns is None: 774 return 775 for idx in range(len(columns)): 776 self.InsertColumn(idx, columns[idx])
777 #------------------------------------------------------------
778 - def set_column_widths(self, widths=None):
779 """Set the column width policy. 780 781 widths = None: 782 use previous policy if any or default policy 783 widths != None: 784 use this policy and remember it for later calls 785 786 This means there is no way to *revert* to the default policy :-( 787 """ 788 # explicit policy ? 789 if widths is not None: 790 self.__widths = widths 791 for idx in range(len(self.__widths)): 792 self.SetColumnWidth(col = idx, width = self.__widths[idx]) 793 return 794 795 # previous policy ? 796 if self.__widths is not None: 797 for idx in range(len(self.__widths)): 798 self.SetColumnWidth(col = idx, width = self.__widths[idx]) 799 return 800 801 # default policy ! 802 if self.GetItemCount() == 0: 803 width_type = wx.LIST_AUTOSIZE_USEHEADER 804 else: 805 width_type = wx.LIST_AUTOSIZE 806 for idx in range(self.GetColumnCount()): 807 self.SetColumnWidth(col = idx, width = width_type)
808 #------------------------------------------------------------
809 - def set_string_items(self, items = None):
810 """All item members must be unicode()able or None.""" 811 812 self.DeleteAllItems() 813 self.__data = items 814 self.__tt_last_item = None 815 816 if items is None: 817 return 818 819 for item in items: 820 try: 821 item[0] 822 if not isinstance(item, basestring): 823 is_numerically_iterable = True 824 else: 825 is_numerically_iterable = False 826 except TypeError: 827 is_numerically_iterable = False 828 829 if is_numerically_iterable: 830 # cannot use errors='replace' since then 831 # None/ints/unicode strings fail to get encoded 832 col_val = unicode(item[0]) 833 row_num = self.InsertStringItem(index = sys.maxint, label = col_val) 834 for col_idx in range(1, min(self.GetColumnCount(), len(item))): 835 col_val = unicode(item[col_idx]) 836 self.SetStringItem(index = row_num, col = col_idx, label = col_val) 837 else: 838 # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded 839 col_val = unicode(item) 840 row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
841 #------------------------------------------------------------
842 - def set_data(self, data = None):
843 """<data must be a list corresponding to the item indices>""" 844 self.__data = data 845 self.__tt_last_item = None
846 #------------------------------------------------------------
847 - def set_selections(self, selections=None):
848 self.Select(0, on = 0) 849 if selections is None: 850 return 851 for idx in selections: 852 self.Select(idx = idx, on = 1)
853 #self.SetItemState(idx, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) 854
855 - def __get_selections(self):
856 if self.__is_single_selection: 857 return [self.GetFirstSelected()] 858 selections = [] 859 idx = self.GetFirstSelected() 860 while idx != -1: 861 selections.append(idx) 862 idx = self.GetNextSelected(idx) 863 return selections
864 865 selections = property(__get_selections, set_selections) 866 #------------------------------------------------------------ 867 # getters 868 #------------------------------------------------------------
869 - def get_column_labels(self):
870 labels = [] 871 for col_idx in self.GetColumnCount(): 872 col = self.GetColumn(col = col_idx) 873 labels.append(col.GetText()) 874 return labels
875 #------------------------------------------------------------
876 - def get_item(self, item_idx=None):
877 if item_idx is not None: 878 return self.GetItem(item_idx)
879 #------------------------------------------------------------
880 - def get_items(self):
881 return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
882 #------------------------------------------------------------
883 - def get_string_items(self):
884 return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
885 #------------------------------------------------------------
886 - def get_selected_items(self, only_one=False):
887 888 if self.__is_single_selection or only_one: 889 return self.GetFirstSelected() 890 891 items = [] 892 idx = self.GetFirstSelected() 893 while idx != -1: 894 items.append(idx) 895 idx = self.GetNextSelected(idx) 896 897 return items
898 #------------------------------------------------------------
899 - def get_selected_string_items(self, only_one=False):
900 901 if self.__is_single_selection or only_one: 902 return self.GetItemText(self.GetFirstSelected()) 903 904 items = [] 905 idx = self.GetFirstSelected() 906 while idx != -1: 907 items.append(self.GetItemText(idx)) 908 idx = self.GetNextSelected(idx) 909 910 return items
911 #------------------------------------------------------------
912 - def get_item_data(self, item_idx = None):
913 if self.__data is None: # this isn't entirely clean 914 return None 915 916 if item_idx is not None: 917 return self.__data[item_idx] 918 919 return [ self.__data[item_idx] for item_idx in range(self.GetItemCount()) ]
920 #------------------------------------------------------------
921 - def get_selected_item_data(self, only_one=False):
922 923 if self.__is_single_selection or only_one: 924 if self.__data is None: 925 return None 926 idx = self.GetFirstSelected() 927 if idx == -1: 928 return None 929 return self.__data[idx] 930 931 data = [] 932 if self.__data is None: 933 return data 934 idx = self.GetFirstSelected() 935 while idx != -1: 936 data.append(self.__data[idx]) 937 idx = self.GetNextSelected(idx) 938 939 return data
940 #------------------------------------------------------------
941 - def deselect_selected_item(self):
942 self.Select(idx = self.GetFirstSelected(), on = 0)
943 #------------------------------------------------------------
944 - def remove_item(self, item_idx=None):
945 self.DeleteItem(item_idx) 946 if self.__data is not None: 947 del self.__data[item_idx] 948 self.__tt_last_item = None
949 #------------------------------------------------------------ 950 # event handlers 951 #------------------------------------------------------------
952 - def _on_list_item_activated(self, event):
953 event.Skip() 954 if self.__activate_callback is not None: 955 self.__activate_callback(event)
956 #------------------------------------------------------------
957 - def _on_mouse_motion(self, event):
958 """Update tooltip on mouse motion. 959 960 for s in dir(wx): 961 if s.startswith('LIST_HITTEST'): 962 print s, getattr(wx, s) 963 964 LIST_HITTEST_ABOVE 1 965 LIST_HITTEST_BELOW 2 966 LIST_HITTEST_NOWHERE 4 967 LIST_HITTEST_ONITEM 672 968 LIST_HITTEST_ONITEMICON 32 969 LIST_HITTEST_ONITEMLABEL 128 970 LIST_HITTEST_ONITEMRIGHT 256 971 LIST_HITTEST_ONITEMSTATEICON 512 972 LIST_HITTEST_TOLEFT 1024 973 LIST_HITTEST_TORIGHT 2048 974 """ 975 item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y)) 976 977 # pointer on item related area at all ? 978 if where_flag not in [ 979 wx.LIST_HITTEST_ONITEMLABEL, 980 wx.LIST_HITTEST_ONITEMICON, 981 wx.LIST_HITTEST_ONITEMSTATEICON, 982 wx.LIST_HITTEST_ONITEMRIGHT, 983 wx.LIST_HITTEST_ONITEM 984 ]: 985 self.__tt_last_item = None # not on any item 986 self.SetToolTipString(self.__tt_static_part) 987 return 988 989 # same item as last time around ? 990 if self.__tt_last_item == item_idx: 991 return 992 993 # remeber the new item we are on 994 self.__tt_last_item = item_idx 995 996 # HitTest() can return -1 if it so pleases, meaning that no item 997 # was hit or else that maybe there aren't any items (empty list) 998 if item_idx == wx.NOT_FOUND: 999 self.SetToolTipString(self.__tt_static_part) 1000 return 1001 1002 # do we *have* item data ? 1003 if self.__data is None: 1004 self.SetToolTipString(self.__tt_static_part) 1005 return 1006 1007 # under some circumstances the item_idx returned 1008 # by HitTest() may be out of bounds with respect to 1009 # self.__data, this hints at a sync problem between 1010 # setting display items and associated data 1011 if ( 1012 (item_idx > (len(self.__data) - 1)) 1013 or 1014 (item_idx < -1) 1015 ): 1016 self.SetToolTipString(self.__tt_static_part) 1017 print "*************************************************************" 1018 print "GNUmed has detected an inconsistency with list item tooltips." 1019 print "" 1020 print "This is not a big problem and you can keep working." 1021 print "" 1022 print "However, please send us the following so we can fix GNUmed:" 1023 print "" 1024 print "item idx: %s" % item_idx 1025 print 'where flag: %s' % where_flag 1026 print 'data list length: %s' % len(self.__data) 1027 print "*************************************************************" 1028 return 1029 1030 dyna_tt = None 1031 if self.__item_tooltip_callback is not None: 1032 dyna_tt = self.__item_tooltip_callback(self.__data[item_idx]) 1033 1034 if dyna_tt is None: 1035 self.SetToolTipString(self.__tt_static_part) 1036 return 1037 1038 self.SetToolTipString(dyna_tt)
1039 #------------------------------------------------------------ 1040 # properties 1041 #------------------------------------------------------------
1042 - def _get_activate_callback(self):
1043 return self.__activate_callback
1044
1045 - def _set_activate_callback(self, callback):
1046 if callback is None: 1047 self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED) 1048 else: 1049 if not callable(callback): 1050 raise ValueError('<activate> callback is not a callable: %s' % callback) 1051 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated) 1052 self.__activate_callback = callback
1053 1054 activate_callback = property(_get_activate_callback, _set_activate_callback) 1055 #------------------------------------------------------------
1056 - def _set_item_tooltip_callback(self, callback):
1057 if callback is not None: 1058 if not callable(callback): 1059 raise ValueError('<item_tooltip> callback is not a callable: %s' % callback) 1060 self.__item_tooltip_callback = callback
1061 1062 # the callback must be a function which takes a single argument 1063 # the argument is the data for the item the tooltip is on 1064 # the callback must return None if no item tooltip is to be shown 1065 # otherwise it must return a string (possibly with \n) 1066 item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
1067 #================================================================ 1068 # main 1069 #---------------------------------------------------------------- 1070 if __name__ == '__main__': 1071 1072 if len(sys.argv) < 2: 1073 sys.exit() 1074 1075 if sys.argv[1] != 'test': 1076 sys.exit() 1077 1078 from Gnumed.pycommon import gmI18N 1079 gmI18N.activate_locale() 1080 gmI18N.install_domain() 1081 1082 #------------------------------------------------------------
1083 - def test_wxMultiChoiceDialog():
1084 app = wx.PyWidgetTester(size = (400, 500)) 1085 dlg = wx.MultiChoiceDialog ( 1086 parent = None, 1087 message = 'test message', 1088 caption = 'test caption', 1089 choices = ['a', 'b', 'c', 'd', 'e'] 1090 ) 1091 dlg.ShowModal() 1092 sels = dlg.GetSelections() 1093 print "selected:" 1094 for sel in sels: 1095 print sel
1096 #------------------------------------------------------------
1097 - def test_get_choices_from_list():
1098 1099 def edit(argument): 1100 print "editor called with:" 1101 print argument
1102 1103 def refresh(lctrl): 1104 choices = ['a', 'b', 'c'] 1105 lctrl.set_string_items(choices) 1106 1107 app = wx.PyWidgetTester(size = (200, 50)) 1108 chosen = get_choices_from_list ( 1109 # msg = 'select a health issue\nfrom the list below\n', 1110 caption = 'select health issues', 1111 #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']], 1112 #columns = ['issue', 'no of episodes'] 1113 columns = ['issue'], 1114 refresh_callback = refresh 1115 #, edit_callback = edit 1116 ) 1117 print "chosen:" 1118 print chosen 1119 #------------------------------------------------------------
1120 - def test_item_picker_dlg():
1121 app = wx.PyWidgetTester(size = (200, 50)) 1122 dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:') 1123 dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy']) 1124 #dlg.set_columns(['Plugins'], []) 1125 dlg.set_string_items(['patient', 'emr', 'docs']) 1126 result = dlg.ShowModal() 1127 print result 1128 print dlg.get_picks()
1129 #------------------------------------------------------------ 1130 #test_get_choices_from_list() 1131 #test_wxMultiChoiceDialog() 1132 test_item_picker_dlg() 1133 1134 #================================================================ 1135 # 1136