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          sorting: http://code.activestate.com/recipes/426407/
      15  """
      16  #================================================================
      17  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      18  __license__ = "GPL v2 or later"
      19
      20
      21  import sys
      22  import types
      23  import logging
      24  import thread
      25  import time
      26  import re as regex
      27
      28
      29  import wx
      30  import wx.lib.mixins.listctrl as listmixins
      31
      32
      33  _log = logging.getLogger('gm.list_ui')
      34  #================================================================
      35  # FIXME: configurable callback on double-click action
      36
      37 -def get_choices_from_list (
      38                          parent=None,
      39                          msg=None,
      40                          caption=None,
      41                          columns=None,
      42                          choices=None,
      43                          data=None,
      44                          selections=None,
      45                          edit_callback=None,
      46                          new_callback=None,
      47                          delete_callback=None,
      48                          refresh_callback=None,
      49                          single_selection=False,
      50                          can_return_empty=False,
      51                          ignore_OK_button=False,
      52                          left_extra_button=None,
      53                          middle_extra_button=None,
      54                          right_extra_button=None,
      55                          list_tooltip_callback=None):
      56          """Let user select item(s) from a list.
      57
      58          - new_callback: ()
      59          - edit_callback: (item data)
      60          - delete_callback: (item data)
      61          - refresh_callback: (listctrl)
      62          - list_tooltip_callback: (item data)
      63
      64          - left/middle/right_extra_button: (label, tooltip, <callback>)
      65                  <callback> is called with item_data as the only argument
      66
      67          returns:
      68                  on [CANCEL]: None
      69                  on [OK]:
      70                          if any items selected:
      71                                  list of selected items
      72                          else:
      73                                  if can_return_empty is True:
      74                                          empty list
      75                                  else:
      76                                          None
      77          """
      78          if caption is None:
      79                  caption = _('generic multi choice dialog')
      80
      81          if single_selection:
      82                  dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg, style = wx.LC_SINGLE_SEL)
      83          else:
      84                  dlg = cGenericListSelectorDlg(parent, -1, title = caption, msg = msg)
      85
      86          dlg.refresh_callback = refresh_callback
      87          dlg.edit_callback = edit_callback
      88          dlg.new_callback = new_callback
      89          dlg.delete_callback = delete_callback
      90          dlg.list_tooltip_callback = list_tooltip_callback
      91
      92          dlg.ignore_OK_button = ignore_OK_button
      93          dlg.left_extra_button = left_extra_button
      94          dlg.middle_extra_button = middle_extra_button
      95          dlg.right_extra_button = right_extra_button
      96
      97          dlg.set_columns(columns = columns)
      98
      99          if refresh_callback is None:
     100                  dlg.set_string_items(items = choices)           # list ctrl will refresh anyway if possible
     101                  dlg.set_column_widths()
     102
     103          if data is not None:
     104                  dlg.set_data(data = data)                                       # can override data set if refresh_callback is not None
     105
     106          if selections is not None:
     107                  dlg.set_selections(selections = selections)
     108          dlg.can_return_empty = can_return_empty
     109
     110          btn_pressed = dlg.ShowModal()
     111          sels = dlg.get_selected_item_data(only_one = single_selection)
     112          dlg.Destroy()
     113
     114          if btn_pressed == wx.ID_OK:
     115                  if can_return_empty and (sels is None):
     116                          return []
     117                  return sels
     118
     119          return None
     120  #----------------------------------------------------------------
     121  from Gnumed.wxGladeWidgets import wxgGenericListSelectorDlg
     122
     123 -class cGenericListSelectorDlg(wxgGenericListSelectorDlg.wxgGenericListSelectorDlg):
     124          """A dialog holding a list and a few buttons to act on the items."""
     125
     126          # FIXME: configurable callback on double-click action
     127
     128 -        def __init__(self, *args, **kwargs):
     129
     130                  try:
     131                          msg = kwargs['msg']
     132                          del kwargs['msg']
     133                  except KeyError: msg = None
     134
     135                  wxgGenericListSelectorDlg.wxgGenericListSelectorDlg.__init__(self, *args, **kwargs)
     136
     137                  self.message = msg
     138
     139                  self.left_extra_button = None
     140                  self.middle_extra_button = None
     141                  self.right_extra_button = None
     142
     143                  self.refresh_callback = None                    # called when new/edit/delete callbacks return True (IOW were not cancelled)
     144                  self.new_callback = None                                # called when NEW button pressed, no argument passed in
     145                  self.edit_callback = None                               # called when EDIT button pressed, data of topmost selected item passed in
     146                  self.delete_callback = None                             # called when DELETE button pressed, data of topmost selected item passed in
     147
     148                  self.can_return_empty = False
     149                  self.ignore_OK_button = False                   # by default do show/use the OK button
     150          #------------------------------------------------------------
     151 -        def set_columns(self, columns=None):
     152                  self._LCTRL_items.set_columns(columns = columns)
     153          #------------------------------------------------------------
     154 -        def set_column_widths(self, widths=None):
     155                  self._LCTRL_items.set_column_widths(widths = widths)
     156          #------------------------------------------------------------
     157 -        def set_string_items(self, items = None):
     158                  self._LCTRL_items.set_string_items(items = items)
     159                  self._LCTRL_items.set_column_widths()
     160                  self._LCTRL_items.Select(0)
     161          #------------------------------------------------------------
     162 -        def set_selections(self, selections = None):
     163                  self._LCTRL_items.set_selections(selections = selections)
     164                  if selections is None:
     165                          return
     166                  if len(selections) == 0:
     167                          return
     168                  if self.ignore_OK_button:
     169                          return
     170                  self._BTN_ok.Enable(True)
     171                  self._BTN_ok.SetDefault()
     172          #------------------------------------------------------------
     173 -        def set_data(self, data = None):
     174                  self._LCTRL_items.set_data(data = data)
     175          #------------------------------------------------------------
     176 -        def get_selected_item_data(self, only_one=False):
     177                  return self._LCTRL_items.get_selected_item_data(only_one=only_one)
     178          #------------------------------------------------------------
     179          # event handlers
     180          #------------------------------------------------------------
     181 -        def _on_list_item_selected(self, event):
     182                  if not self.__ignore_OK_button:
     183                          self._BTN_ok.SetDefault()
     184                          self._BTN_ok.Enable(True)
     185
     186                  if self.edit_callback is not None:
     187                          self._BTN_edit.Enable(True)
     188
     189                  if self.delete_callback is not None:
     190                          self._BTN_delete.Enable(True)
     191          #------------------------------------------------------------
     192 -        def _on_list_item_deselected(self, event):
     193                  if self._LCTRL_items.get_selected_items(only_one=True) == -1:
     194                          if not self.can_return_empty:
     195                                  self._BTN_cancel.SetDefault()
     196                                  self._BTN_ok.Enable(False)
     197                          self._BTN_edit.Enable(False)
     198                          self._BTN_delete.Enable(False)
     199          #------------------------------------------------------------
     200 -        def _on_new_button_pressed(self, event):
     201                  if not self.new_callback():
     202                          self._LCTRL_items.SetFocus()
     203                          return
     204                  if self.refresh_callback is None:
     205                          self._LCTRL_items.SetFocus()
     206                          return
     207                  wx.BeginBusyCursor()
     208                  try:
     209                          self.refresh_callback(lctrl = self._LCTRL_items)
     210                  finally:
     211                          wx.EndBusyCursor()
     212                  self._LCTRL_items.set_column_widths()
     213                  self._LCTRL_items.SetFocus()
     214          #------------------------------------------------------------
     215 -        def _on_edit_button_pressed(self, event):
     216                  # if the edit button *can* be pressed there are *supposed*
     217                  # to be both an item selected and an editor configured
     218                  if not self.edit_callback(self._LCTRL_items.get_selected_item_data(only_one=True)):
     219                          self._LCTRL_items.SetFocus()
     220                          return
     221                  if self.refresh_callback is None:
     222                          self._LCTRL_items.SetFocus()
     223                          return
     224                  wx.BeginBusyCursor()
     225                  try:
     226                          self.refresh_callback(lctrl = self._LCTRL_items)
     227                  finally:
     228                          wx.EndBusyCursor()
     229                  self._LCTRL_items.set_column_widths()
     230                  self._LCTRL_items.SetFocus()
     231          #------------------------------------------------------------
     232 -        def _on_delete_button_pressed(self, event):
     233                  # if the delete button *can* be pressed there are *supposed*
     234                  # to be both an item selected and a deletor configured
     235                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     236                  if item_data is None:
     237                          self._LCTRL_items.SetFocus()
     238                          return
     239                  if not self.delete_callback(item_data):
     240                          self._LCTRL_items.SetFocus()
     241                          return
     242                  if self.refresh_callback is None:
     243                          self._LCTRL_items.SetFocus()
     244                          return
     245                  wx.BeginBusyCursor()
     246                  try:
     247                          self.refresh_callback(lctrl = self._LCTRL_items)
     248                  finally:
     249                          wx.EndBusyCursor()
     250                  self._LCTRL_items.set_column_widths()
     251                  self._LCTRL_items.SetFocus()
     252          #------------------------------------------------------------
     253 -        def _on_left_extra_button_pressed(self, event):
     254                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     255                  if not self.__left_extra_button_callback(item_data):
     256                          self._LCTRL_items.SetFocus()
     257                          return
     258                  if self.refresh_callback is None:
     259                          self._LCTRL_items.SetFocus()
     260                          return
     261                  wx.BeginBusyCursor()
     262                  try:
     263                          self.refresh_callback(lctrl = self._LCTRL_items)
     264                  finally:
     265                          wx.EndBusyCursor()
     266                  self._LCTRL_items.set_column_widths()
     267                  self._LCTRL_items.SetFocus()
     268          #------------------------------------------------------------
     269 -        def _on_middle_extra_button_pressed(self, event):
     270                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     271                  if not self.__middle_extra_button_callback(item_data):
     272                          self._LCTRL_items.SetFocus()
     273                          return
     274                  if self.refresh_callback is None:
     275                          self._LCTRL_items.SetFocus()
     276                          return
     277                  wx.BeginBusyCursor()
     278                  try:
     279                          self.refresh_callback(lctrl = self._LCTRL_items)
     280                  finally:
     281                          wx.EndBusyCursor()
     282                  self._LCTRL_items.set_column_widths()
     283                  self._LCTRL_items.SetFocus()
     284          #------------------------------------------------------------
     285 -        def _on_right_extra_button_pressed(self, event):
     286                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     287                  if not self.__right_extra_button_callback(item_data):
     288                          self._LCTRL_items.SetFocus()
     289                          return
     290                  if self.refresh_callback is None:
     291                          self._LCTRL_items.SetFocus()
     292                          return
     293                  wx.BeginBusyCursor()
     294                  try:
     295                          self.refresh_callback(lctrl = self._LCTRL_items)
     296                  finally:
     297                          wx.EndBusyCursor()
     298                  self._LCTRL_items.set_column_widths()
     299                  self._LCTRL_items.SetFocus()
     300          #------------------------------------------------------------
     301          # properties
     302          #------------------------------------------------------------
     303 -        def _set_ignore_OK_button(self, ignored):
     304                  self.__ignore_OK_button = ignored
     305                  if self.__ignore_OK_button:
     306                          self._BTN_ok.Hide()
     307                          self._BTN_ok.Enable(False)
     308                  else:
     309                          self._BTN_ok.Show()
     310                          if self._LCTRL_items.get_selected_items(only_one=True) == -1:
     311                                  if self.can_return_empty:
     312                                          self._BTN_ok.Enable(True)
     313                                  else:
     314                                          self._BTN_ok.Enable(False)
     315                                          self._BTN_cancel.SetDefault()
     316
     317          ignore_OK_button = property(lambda x:x, _set_ignore_OK_button)
     318          #------------------------------------------------------------
     319 -        def _set_left_extra_button(self, definition):
     320                  if definition is None:
     321                          self._BTN_extra_left.Enable(False)
     322                          self._BTN_extra_left.Hide()
     323                          self.__left_extra_button_callback = None
     324                          return
     325
     326                  (label, tooltip, callback) = definition
     327                  if not callable(callback):
     328                          raise ValueError('<left extra button> callback is not a callable: %s' % callback)
     329                  self.__left_extra_button_callback = callback
     330                  self._BTN_extra_left.SetLabel(label)
     331                  self._BTN_extra_left.SetToolTipString(tooltip)
     332                  self._BTN_extra_left.Enable(True)
     333                  self._BTN_extra_left.Show()
     334
     335          left_extra_button = property(lambda x:x, _set_left_extra_button)
     336          #------------------------------------------------------------
     337 -        def _set_middle_extra_button(self, definition):
     338                  if definition is None:
     339                          self._BTN_extra_middle.Enable(False)
     340                          self._BTN_extra_middle.Hide()
     341                          self.__middle_extra_button_callback = None
     342                          return
     343
     344                  (label, tooltip, callback) = definition
     345                  if not callable(callback):
     346                          raise ValueError('<middle extra button> callback is not a callable: %s' % callback)
     347                  self.__middle_extra_button_callback = callback
     348                  self._BTN_extra_middle.SetLabel(label)
     349                  self._BTN_extra_middle.SetToolTipString(tooltip)
     350                  self._BTN_extra_middle.Enable(True)
     351                  self._BTN_extra_middle.Show()
     352
     353          middle_extra_button = property(lambda x:x, _set_middle_extra_button)
     354          #------------------------------------------------------------
     355 -        def _set_right_extra_button(self, definition):
     356                  if definition is None:
     357                          self._BTN_extra_right.Enable(False)
     358                          self._BTN_extra_right.Hide()
     359                          self.__right_extra_button_callback = None
     360                          return
     361
     362                  (label, tooltip, callback) = definition
     363                  if not callable(callback):
     364                          raise ValueError('<right extra button> callback is not a callable: %s' % callback)
     365                  self.__right_extra_button_callback = callback
     366                  self._BTN_extra_right.SetLabel(label)
     367                  self._BTN_extra_right.SetToolTipString(tooltip)
     368                  self._BTN_extra_right.Enable(True)
     369                  self._BTN_extra_right.Show()
     370
     371          right_extra_button = property(lambda x:x, _set_right_extra_button)
     372          #------------------------------------------------------------
     373 -        def _get_new_callback(self):
     374                  return self.__new_callback
     375
     376 -        def _set_new_callback(self, callback):
     377                  if callback is not None:
     378                          if self.refresh_callback is None:
     379                                  raise ValueError('refresh callback must be set before new callback can be set')
     380                          if not callable(callback):
     381                                  raise ValueError('<new> callback is not a callable: %s' % callback)
     382                  self.__new_callback = callback
     383
     384                  if callback is None:
     385                          self._BTN_new.Enable(False)
     386                          self._BTN_new.Hide()
     387                  else:
     388                          self._BTN_new.Enable(True)
     389                          self._BTN_new.Show()
     390
     391          new_callback = property(_get_new_callback, _set_new_callback)
     392          #------------------------------------------------------------
     393 -        def _get_edit_callback(self):
     394                  return self.__edit_callback
     395
     396 -        def _set_edit_callback(self, callback):
     397                  if callback is not None:
     398                          if not callable(callback):
     399                                  raise ValueError('<edit> callback is not a callable: %s' % callback)
     400                  self.__edit_callback = callback
     401
     402                  if callback is None:
     403                          self._BTN_edit.Enable(False)
     404                          self._BTN_edit.Hide()
     405                  else:
     406                          self._BTN_edit.Enable(True)
     407                          self._BTN_edit.Show()
     408
     409          edit_callback = property(_get_edit_callback, _set_edit_callback)
     410          #------------------------------------------------------------
     411 -        def _get_delete_callback(self):
     412                  return self.__delete_callback
     413
     414 -        def _set_delete_callback(self, callback):
     415                  if callback is not None:
     416                          if self.refresh_callback is None:
     417                                  raise ValueError('refresh callback must be set before delete callback can be set')
     418                          if not callable(callback):
     419                                  raise ValueError('<delete> callback is not a callable: %s' % callback)
     420                  self.__delete_callback = callback
     421
     422                  if callback is None:
     423                          self._BTN_delete.Enable(False)
     424                          self._BTN_delete.Hide()
     425                  else:
     426                          self._BTN_delete.Enable(True)
     427                          self._BTN_delete.Show()
     428
     429          delete_callback = property(_get_delete_callback, _set_delete_callback)
     430          #------------------------------------------------------------
     431 -        def _get_refresh_callback(self):
     432                  return self.__refresh_callback
     433
     434 -        def _set_refresh_callback_helper(self):
     435                  wx.BeginBusyCursor()
     436                  try:
     437                          self.refresh_callback(lctrl = self._LCTRL_items)
     438                  finally:
     439                          wx.EndBusyCursor()
     440                  self._LCTRL_items.set_column_widths()
     441
     442 -        def _set_refresh_callback(self, callback):
     443                  if callback is not None:
     444                          if not callable(callback):
     445                                  raise ValueError('<refresh> callback is not a callable: %s' % callback)
     446                  self.__refresh_callback = callback
     447                  if callback is not None:
     448                          wx.CallAfter(self._set_refresh_callback_helper)
     449
     450          refresh_callback = property(_get_refresh_callback, _set_refresh_callback)
     451          #------------------------------------------------------------
     452 -        def _set_list_tooltip_callback(self, callback):
     453                  self._LCTRL_items.item_tooltip_callback = callback
     454
     455          list_tooltip_callback = property(lambda x:x, _set_list_tooltip_callback)
     456          #def _get_tooltip(self, item):          # inside a class
     457          #def _get_tooltip(item):                        # outside a class
     458          #------------------------------------------------------------
     459 -        def _set_message(self, message):
     460                  if message is None:
     461                          self._LBL_message.Hide()
     462                          return
     463                  self._LBL_message.SetLabel(message)
     464                  self._LBL_message.Show()
     465
     466          message = property(lambda x:x, _set_message)
     467  #================================================================
     468  from Gnumed.wxGladeWidgets import wxgGenericListManagerPnl
     469
     470 -class cGenericListManagerPnl(wxgGenericListManagerPnl.wxgGenericListManagerPnl):
     471          """A panel holding a generic multi-column list and action buttions."""
     472
     473 -        def __init__(self, *args, **kwargs):
     474
     475                  try:
     476                          msg = kwargs['msg']
     477                          del kwargs['msg']
     478                  except KeyError: msg = None
     479
     480                  wxgGenericListManagerPnl.wxgGenericListManagerPnl.__init__(self, *args, **kwargs)
     481
     482                  if msg is None:
     483                          self._LBL_message.Hide()
     484                  else:
     485                          self._LBL_message.SetLabel(msg)
     486
     487                  # new/edit/delete must return True/False to enable refresh
     488                  self.__new_callback = None                              # called when NEW button pressed, no argument passed in
     489                  self.edit_callback = None                               # called when EDIT button pressed, data of topmost selected item passed in
     490                  self.delete_callback = None                             # called when DELETE button pressed, data of topmost selected item passed in
     491                  self.refresh_callback = None                    # called when new/edit/delete callbacks return True (IOW were not cancelled)
     492
     493                  self.__select_callback = None                   # called when an item is selected, data of topmost selected item passed in
     494
     495                  self.left_extra_button = None
     496                  self.middle_extra_button = None
     497                  self.right_extra_button = None
     498          #------------------------------------------------------------
     499          # external API
     500          #------------------------------------------------------------
     501 -        def set_columns(self, columns=None):
     502                  self._LCTRL_items.set_columns(columns = columns)
     503          #------------------------------------------------------------
     504 -        def set_string_items(self, items = None):
     505                  self._LCTRL_items.set_string_items(items = items)
     506                  self._LCTRL_items.set_column_widths()
     507
     508                  if (items is None) or (len(items) == 0):
     509                          self._BTN_edit.Enable(False)
     510                          self._BTN_remove.Enable(False)
     511                  else:
     512                          self._LCTRL_items.Select(0)
     513          #------------------------------------------------------------
     514 -        def set_selections(self, selections = None):
     515                  self._LCTRL_items.set_selections(selections = selections)
     516          #------------------------------------------------------------
     517 -        def set_data(self, data = None):
     518                  self._LCTRL_items.set_data(data = data)
     519          #------------------------------------------------------------
     520 -        def get_selected_item_data(self, only_one=False):
     521                  return self._LCTRL_items.get_selected_item_data(only_one=only_one)
     522          #------------------------------------------------------------
     523          # event handlers
     524          #------------------------------------------------------------
     525 -        def _on_list_item_selected(self, event):
     526                  if self.edit_callback is not None:
     527                          self._BTN_edit.Enable(True)
     528                  if self.delete_callback is not None:
     529                          self._BTN_remove.Enable(True)
     530                  if self.__select_callback is not None:
     531                          item = self._LCTRL_items.get_selected_item_data(only_one=True)
     532                          self.__select_callback(item)
     533          #------------------------------------------------------------
     534 -        def _on_list_item_deselected(self, event):
     535                  if self._LCTRL_items.get_selected_items(only_one=True) == -1:
     536                          self._BTN_edit.Enable(False)
     537                          self._BTN_remove.Enable(False)
     538                          if self.__select_callback is not None:
     539                                  self.__select_callback(None)
     540          #------------------------------------------------------------
     541 -        def _on_add_button_pressed(self, event):
     542                  if not self.new_callback():
     543                          return
     544                  if self.refresh_callback is None:
     545                          return
     546                  wx.BeginBusyCursor()
     547                  try:
     548                          self.refresh_callback(lctrl = self._LCTRL_items)
     549                  finally:
     550                          wx.EndBusyCursor()
     551          #------------------------------------------------------------
     552 -        def _on_list_item_activated(self, event):
     553                  if self.edit_callback is None:
     554                          return
     555                  self._on_edit_button_pressed(event)
     556          #------------------------------------------------------------
     557 -        def _on_edit_button_pressed(self, event):
     558                  item = self._LCTRL_items.get_selected_item_data(only_one=True)
     559                  if item is None:
     560                          return
     561                  if not self.edit_callback(item):
     562                          return
     563                  if self.refresh_callback is None:
     564                          return
     565                  wx.BeginBusyCursor()
     566                  try:
     567                          self.refresh_callback(lctrl = self._LCTRL_items)
     568                  finally:
     569                          wx.EndBusyCursor()
     570          #------------------------------------------------------------
     571 -        def _on_remove_button_pressed(self, event):
     572                  item = self._LCTRL_items.get_selected_item_data(only_one=True)
     573                  if item is None:
     574                          return
     575                  if not self.delete_callback(item):
     576                          return
     577                  if self.refresh_callback is None:
     578                          return
     579                  wx.BeginBusyCursor()
     580                  try:
     581                          self.refresh_callback(lctrl = self._LCTRL_items)
     582                  finally:
     583                          wx.EndBusyCursor()
     584          #------------------------------------------------------------
     585 -        def _on_left_extra_button_pressed(self, event):
     586                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     587                  if not self.__left_extra_button_callback(item_data):
     588                          self._LCTRL_items.SetFocus()
     589                          return
     590                  if self.refresh_callback is None:
     591                          self._LCTRL_items.SetFocus()
     592                          return
     593                  wx.BeginBusyCursor()
     594                  try:
     595                          self.refresh_callback(lctrl = self._LCTRL_items)
     596                  finally:
     597                          wx.EndBusyCursor()
     598                  self._LCTRL_items.set_column_widths()
     599                  self._LCTRL_items.SetFocus()
     600          #------------------------------------------------------------
     601 -        def _on_middle_extra_button_pressed(self, event):
     602                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     603                  if not self.__middle_extra_button_callback(item_data):
     604                          self._LCTRL_items.SetFocus()
     605                          return
     606                  if self.refresh_callback is None:
     607                          self._LCTRL_items.SetFocus()
     608                          return
     609                  wx.BeginBusyCursor()
     610                  try:
     611                          self.refresh_callback(lctrl = self._LCTRL_items)
     612                  finally:
     613                          wx.EndBusyCursor()
     614                  self._LCTRL_items.set_column_widths()
     615                  self._LCTRL_items.SetFocus()
     616          #------------------------------------------------------------
     617 -        def _on_right_extra_button_pressed(self, event):
     618                  item_data = self._LCTRL_items.get_selected_item_data(only_one=True)
     619                  if not self.__right_extra_button_callback(item_data):
     620                          self._LCTRL_items.SetFocus()
     621                          return
     622                  if self.refresh_callback is None:
     623                          self._LCTRL_items.SetFocus()
     624                          return
     625                  wx.BeginBusyCursor()
     626                  try:
     627                          self.refresh_callback(lctrl = self._LCTRL_items)
     628                  finally:
     629                          wx.EndBusyCursor()
     630                  self._LCTRL_items.set_column_widths()
     631                  self._LCTRL_items.SetFocus()
     632          #------------------------------------------------------------
     633          # properties
     634          #------------------------------------------------------------
     635 -        def _get_new_callback(self):
     636                  return self.__new_callback
     637
     638 -        def _set_new_callback(self, callback):
     639                  if callback is not None:
     640                          if not callable(callback):
     641                                  raise ValueError('<new> callback is not a callable: %s' % callback)
     642                  self.__new_callback = callback
     643                  self._BTN_add.Enable(callback is not None)
     644
     645          new_callback = property(_get_new_callback, _set_new_callback)
     646          #------------------------------------------------------------
     647 -        def _get_select_callback(self):
     648                  return self.__select_callback
     649
     650 -        def _set_select_callback(self, callback):
     651                  if callback is not None:
     652                          if not callable(callback):
     653                                  raise ValueError('<select> callback is not a callable: %s' % callback)
     654                  self.__select_callback = callback
     655
     656          select_callback = property(_get_select_callback, _set_select_callback)
     657          #------------------------------------------------------------
     658 -        def _get_message(self):
     659                  return self._LBL_message.GetLabel()
     660
     661 -        def _set_message(self, msg):
     662                  if msg is None:
     663                          self._LBL_message.Hide()
     664                          self._LBL_message.SetLabel(u'')
     665                  else:
     666                          self._LBL_message.SetLabel(msg)
     667                          self._LBL_message.Show()
     668                  self.Layout()
     669
     670          message = property(_get_message, _set_message)
     671          #------------------------------------------------------------
     672 -        def _set_left_extra_button(self, definition):
     673                  if definition is None:
     674                          self._BTN_extra_left.Enable(False)
     675                          self._BTN_extra_left.Hide()
     676                          self.__left_extra_button_callback = None
     677                          return
     678
     679                  (label, tooltip, callback) = definition
     680                  if not callable(callback):
     681                          raise ValueError('<left extra button> callback is not a callable: %s' % callback)
     682                  self.__left_extra_button_callback = callback
     683                  self._BTN_extra_left.SetLabel(label)
     684                  self._BTN_extra_left.SetToolTipString(tooltip)
     685                  self._BTN_extra_left.Enable(True)
     686                  self._BTN_extra_left.Show()
     687
     688          left_extra_button = property(lambda x:x, _set_left_extra_button)
     689          #------------------------------------------------------------
     690 -        def _set_middle_extra_button(self, definition):
     691                  if definition is None:
     692                          self._BTN_extra_middle.Enable(False)
     693                          self._BTN_extra_middle.Hide()
     694                          self.__middle_extra_button_callback = None
     695                          return
     696
     697                  (label, tooltip, callback) = definition
     698                  if not callable(callback):
     699                          raise ValueError('<middle extra button> callback is not a callable: %s' % callback)
     700                  self.__middle_extra_button_callback = callback
     701                  self._BTN_extra_middle.SetLabel(label)
     702                  self._BTN_extra_middle.SetToolTipString(tooltip)
     703                  self._BTN_extra_middle.Enable(True)
     704                  self._BTN_extra_middle.Show()
     705
     706          middle_extra_button = property(lambda x:x, _set_middle_extra_button)
     707          #------------------------------------------------------------
     708 -        def _set_right_extra_button(self, definition):
     709                  if definition is None:
     710                          self._BTN_extra_right.Enable(False)
     711                          self._BTN_extra_right.Hide()
     712                          self.__right_extra_button_callback = None
     713                          return
     714
     715                  (label, tooltip, callback) = definition
     716                  if not callable(callback):
     717                          raise ValueError('<right extra button> callback is not a callable: %s' % callback)
     718                  self.__right_extra_button_callback = callback
     719                  self._BTN_extra_right.SetLabel(label)
     720                  self._BTN_extra_right.SetToolTipString(tooltip)
     721                  self._BTN_extra_right.Enable(True)
     722                  self._BTN_extra_right.Show()
     723
     724          right_extra_button = property(lambda x:x, _set_right_extra_button)
     725  #================================================================
     726  from Gnumed.wxGladeWidgets import wxgItemPickerDlg
     727
     728 -class cItemPickerDlg(wxgItemPickerDlg.wxgItemPickerDlg):
     729
     730 -        def __init__(self, *args, **kwargs):
     731
     732                  try:
     733                          msg = kwargs['msg']
     734                          del kwargs['msg']
     735                  except KeyError:
     736                          msg = None
     737
     738                  wxgItemPickerDlg.wxgItemPickerDlg.__init__(self, *args, **kwargs)
     739
     740                  if msg is None:
     741                          self._LBL_msg.Hide()
     742                  else:
     743                          self._LBL_msg.SetLabel(msg)
     744
     745                  self.allow_duplicate_picks = True
     746
     747                  self._LCTRL_left.activate_callback = self.__pick_selected
     748                  self.__extra_button_callback = None
     749
     750                  self._LCTRL_left.SetFocus()
     751          #------------------------------------------------------------
     752          # external API
     753          #------------------------------------------------------------
     754 -        def set_columns(self, columns=None, columns_right=None):
     755                  self._LCTRL_left.set_columns(columns = columns)
     756                  if columns_right is None:
     757                          self._LCTRL_right.set_columns(columns = columns)
     758                  else:
     759                          if len(columns_right) < len(columns):
     760                                  cols = columns
     761                          else:
     762                                  cols = columns_right[:len(columns)]
     763                          self._LCTRL_right.set_columns(columns = cols)
     764          #------------------------------------------------------------
     765 -        def set_string_items(self, items = None):
     766                  self._LCTRL_left.set_string_items(items = items)
     767                  self._LCTRL_left.set_column_widths()
     768                  self._LCTRL_right.set_string_items()
     769
     770                  self._BTN_left2right.Enable(False)
     771                  self._BTN_right2left.Enable(False)
     772          #------------------------------------------------------------
     773 -        def set_selections(self, selections = None):
     774                  self._LCTRL_left.set_selections(selections = selections)
     775          #------------------------------------------------------------
     776 -        def set_choices(self, choices=None, data=None):
     777                  self.set_string_items(items = choices)
     778                  if data is not None:
     779                          self.set_data(data = data)
     780          #------------------------------------------------------------
     781 -        def set_picks(self, picks=None, data=None):
     782                  self._LCTRL_right.set_string_items(picks)
     783                  self._LCTRL_right.set_column_widths()
     784                  if data is not None:
     785                          self._LCTRL_right.set_data(data = data)
     786          #------------------------------------------------------------
     787 -        def set_data(self, data = None):
     788                  self._LCTRL_left.set_data(data = data)
     789          #------------------------------------------------------------
     790 -        def get_picks(self):
     791                  return self._LCTRL_right.get_item_data()
     792
     793          picks = property(get_picks, lambda x:x)
     794          #------------------------------------------------------------
     795 -        def _set_extra_button(self, definition):
     796                  if definition is None:
     797                          self._BTN_extra.Enable(False)
     798                          self._BTN_extra.Hide()
     799                          self.__extra_button_callback = None
     800                          return
     801
     802                  (label, tooltip, callback) = definition
     803                  if not callable(callback):
     804                          raise ValueError('<extra button> callback is not a callable: %s' % callback)
     805                  self.__extra_button_callback = callback
     806                  self._BTN_extra.SetLabel(label)
     807                  self._BTN_extra.SetToolTipString(tooltip)
     808                  self._BTN_extra.Enable(True)
     809                  self._BTN_extra.Show()
     810
     811          extra_button = property(lambda x:x, _set_extra_button)
     812          #------------------------------------------------------------
     813          # internal helpers
     814          #------------------------------------------------------------
     815 -        def __pick_selected(self, event=None):
     816                  if self._LCTRL_left.get_selected_items(only_one = True) == -1:
     817                          return
     818
     819                  right_items = self._LCTRL_right.get_string_items()
     820                  right_data = self._LCTRL_right.get_item_data()
     821                  if right_data is None:
     822                          right_data = []
     823
     824                  selected_items = self._LCTRL_left.get_selected_string_items(only_one = False)
     825                  selected_data = self._LCTRL_left.get_selected_item_data(only_one = False)
     826
     827                  if self.allow_duplicate_picks:
     828                          right_items.extend(selected_items)
     829                          right_data.extend(selected_data)
     830                          self._LCTRL_right.set_string_items(items = right_items)
     831                          self._LCTRL_right.set_data(data = right_data)
     832                          self._LCTRL_right.set_column_widths()
     833  #                       print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
     834  #                       print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
     835                          return
     836
     837                  for sel_item, sel_data in zip(selected_items, selected_data):
     838                          if sel_item in right_items:
     839                                  continue
     840                          right_items.append(sel_item)
     841                          right_data.append(sel_data)
     842                  self._LCTRL_right.set_string_items(items = right_items)
     843                  self._LCTRL_right.set_data(data = right_data)
     844                  self._LCTRL_right.set_column_widths()
     845  #               print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
     846  #               print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
     847          #------------------------------------------------------------
     848 -        def __remove_selected_picks(self):
     849                  if self._LCTRL_right.get_selected_items(only_one = True) == -1:
     850                          return
     851
     852                  for item_idx in self._LCTRL_right.get_selected_items(only_one = False):
     853                          self._LCTRL_right.remove_item(item_idx)
     854
     855                  if self._LCTRL_right.GetItemCount() == 0:
     856                          self._BTN_right2left.Enable(False)
     857
     858  #               print u'%s <-> %s (items)' % (self._LCTRL_left.ItemCount, self._LCTRL_right.ItemCount)
     859  #               print u'%s <-> %s (data)' % (len(self._LCTRL_left.data), len(self._LCTRL_right.data))
     860          #------------------------------------------------------------
     861          # event handlers
     862          #------------------------------------------------------------
     863 -        def _on_left_list_item_selected(self, event):
     864                  self._BTN_left2right.Enable(True)
     865          #------------------------------------------------------------
     866 -        def _on_left_list_item_deselected(self, event):
     867                  if self._LCTRL_left.get_selected_items(only_one = True) == -1:
     868                          self._BTN_left2right.Enable(False)
     869          #------------------------------------------------------------
     870 -        def _on_right_list_item_selected(self, event):
     871                  self._BTN_right2left.Enable(True)
     872          #------------------------------------------------------------
     873 -        def _on_right_list_item_deselected(self, event):
     874                  if self._LCTRL_right.get_selected_items(only_one = True) == -1:
     875                          self._BTN_right2left.Enable(False)
     876          #------------------------------------------------------------
     877 -        def _on_button_left2right_pressed(self, event):
     878                  self.__pick_selected()
     879          #------------------------------------------------------------
     880 -        def _on_button_right2left_pressed(self, event):
     881                  self.__remove_selected_picks()
     882          #------------------------------------------------------------
     883 -        def _on_extra_button_pressed(self, event):
     884                  self.__extra_button_callback()
     885          #------------------------------------------------------------
     886 -        def _set_left_item_tooltip_callback(self, callback):
     887                  self._LCTRL_left.item_tooltip_callback = callback
     888
     889          left_item_tooltip_callback = property(lambda x:x, _set_left_item_tooltip_callback)
     890          #------------------------------------------------------------
     891 -        def _set_right_item_tooltip_callback(self, callback):
     892                  self._LCTRL_right.item_tooltip_callback = callback
     893
     894          right_item_tooltip_callback = property(lambda x:x, _set_right_item_tooltip_callback)
     895
     896  #================================================================
     897 -class cReportListCtrl(wx.ListCtrl, listmixins.ListCtrlAutoWidthMixin, listmixins.ColumnSorterMixin):
     898
     899          # sorting: at set_string_items() time all items will be
     900          # adorned with their initial row number as wxPython data,
     901          # this is used later for a) sorting and b) to access
     902          # GNUmed data objects associated with rows,
     903          # the latter are ordered in initial row number order
     904          # at set_data() time
     905
     906          map_item_idx2data_idx = wx.ListCtrl.GetItemData
     907
     908          sort_order_tags = {
     909                  True: u' [\u03b1\u0391 \u2192 \u03c9\u03A9]',
     910                  False: u' [\u03c9\u03A9 \u2192 \u03b1\u0391]'
     911          }
     912
     913 -        def __init__(self, *args, **kwargs):
     914
     915                  self.debug = None
     916
     917                  try:
     918                          kwargs['style'] = kwargs['style'] | wx.LC_REPORT
     919                  except KeyError:
     920                          kwargs['style'] = wx.LC_REPORT
     921
     922                  self.__is_single_selection = ((kwargs['style'] & wx.LC_SINGLE_SEL) == wx.LC_SINGLE_SEL)
     923
     924                  wx.ListCtrl.__init__(self, *args, **kwargs)
     925                  listmixins.ListCtrlAutoWidthMixin.__init__(self)
     926
     927                  # required for column sorting, MUST have this name
     928                  self._invalidate_sorting_metadata()                                     # must be called after each (external/direct) list item update
     929                  listmixins.ColumnSorterMixin.__init__(self, 0)          # must be called again after adding columns (why ?)
     930                  # for debugging sorting:
     931                  #self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click, self)
     932
     933                  self.__widths = None
     934                  self.__data = None
     935                  self.__activate_callback = None
     936                  self.__rightclick_callback = None
     937
     938                  self.__item_tooltip_callback = None
     939                  self.__tt_last_item = None
     940                  self.__tt_static_part = _("""Select the items you want to work on.
     941
     942  A discontinuous selection may depend on your holding down a platform-dependent modifier key (<ctrl>, <alt>, etc) or key combination (eg. <ctrl-shift> or <ctrl-alt>) while clicking.""")
     943                  self.Bind(wx.EVT_MOTION, self._on_mouse_motion)
     944
     945                  self.__next_line_to_search = 0
     946                  self.__search_data = None
     947                  self.__search_dlg = None
     948                  self.__searchable_cols = None
     949  #               self.Bind(wx.EVT_KILL_FOCUS, self._on_lost_focus)
     950                  self.Bind(wx.EVT_CHAR, self._on_char)
     951                  self.Bind(wx.EVT_FIND_CLOSE, self._on_search_dlg_closed)
     952                  self.Bind(wx.EVT_FIND, self._on_search_first_match)
     953                  self.Bind(wx.EVT_FIND_NEXT, self._on_search_next_match)
     954          #------------------------------------------------------------
     955          # setters
     956          #------------------------------------------------------------
     957 -        def set_columns(self, columns=None):
     958                  """(Re)define the columns.
     959
     960                  Note that this will (have to) delete the items.
     961                  """
     962                  self.ClearAll()
     963                  self.__tt_last_item = None
     964                  if columns is None:
     965                          return
     966                  for idx in range(len(columns)):
     967                          self.InsertColumn(idx, columns[idx])
     968
     969                  self._invalidate_sorting_metadata()
     970          #------------------------------------------------------------
     971 -        def set_column_widths(self, widths=None):
     972                  """Set the column width policy.
     973
     974                  widths = None:
     975                          use previous policy if any or default policy
     976                  widths != None:
     977                          use this policy and remember it for later calls
     978
     979                  This means there is no way to *revert* to the default policy :-(
     980                  """
     981                  # explicit policy ?
     982                  if widths is not None:
     983                          self.__widths = widths
     984                          for idx in range(len(self.__widths)):
     985                                  self.SetColumnWidth(col = idx, width = self.__widths[idx])
     986                          return
     987
     988                  # previous policy ?
     989                  if self.__widths is not None:
     990                          for idx in range(len(self.__widths)):
     991                                  self.SetColumnWidth(col = idx, width = self.__widths[idx])
     992                          return
     993
     994                  # default policy !
     995                  if self.GetItemCount() == 0:
     996                          width_type = wx.LIST_AUTOSIZE_USEHEADER
     997                  else:
     998                          width_type = wx.LIST_AUTOSIZE
     999                  for idx in range(self.GetColumnCount()):
    1000                          self.SetColumnWidth(col = idx, width = width_type)
    1001          #------------------------------------------------------------
    1002 -        def set_string_items(self, items=None):
    1003                  """All item members must be unicode()able or None."""
    1004
    1005                  wx.BeginBusyCursor()
    1006                  self._invalidate_sorting_metadata()
    1007
    1008                  # remove existing items
    1009                  loop = 0
    1010                  while True:
    1011                          if loop > 3:
    1012                                  _log.debug('unable to delete list items after looping 3 times, continuing and hoping for the best')
    1013                                  break
    1014                          loop += 1
    1015                          if self.debug is not None:
    1016                                  _log.debug('[round %s] GetItemCount() before DeleteAllItems(): %s (%s, thread [%s])', loop, self.GetItemCount(), self.debug, thread.get_ident())
    1017                          if not self.DeleteAllItems():
    1018                                  _log.debug('DeleteAllItems() failed (%s)', self.debug)
    1019                          item_count = self.GetItemCount()
    1020                          if self.debug is not None:
    1021                                  _log.debug('GetItemCount() after DeleteAllItems(): %s (%s)', item_count, self.debug)
    1022                          if item_count == 0:
    1023                                  break
    1024                          wx.SafeYield(None, True)
    1025                          _log.debug('GetItemCount() not 0 after DeleteAllItems() (%s)', self.debug)
    1026                          time.sleep(0.3)
    1027                          wx.SafeYield(None, True)
    1028
    1029                  if items is None:
    1030                          self.data = None
    1031                          wx.EndBusyCursor()
    1032                          return
    1033
    1034                  # insert new items
    1035                  for item in items:
    1036                          try:
    1037                                  item[0]
    1038                                  if not isinstance(item, basestring):
    1039                                          is_numerically_iterable = True
    1040                                  # do not iterate over individual chars in a string, however
    1041                                  else:
    1042                                          is_numerically_iterable = False
    1043                          except TypeError:
    1044                                  is_numerically_iterable = False
    1045
    1046                          if is_numerically_iterable:
    1047                                  # cannot use errors='replace' since then
    1048                                  # None/ints/unicode strings fail to get encoded
    1049                                  col_val = unicode(item[0])
    1050                                  row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
    1051                                  for col_num in range(1, min(self.GetColumnCount(), len(item))):
    1052                                          col_val = unicode(item[col_num])
    1053                                          self.SetStringItem(index = row_num, col = col_num, label = col_val)
    1054                          else:
    1055                                  # cannot use errors='replace' since then None/ints/unicode strings fails to get encoded
    1056                                  col_val = unicode(item)
    1057                                  row_num = self.InsertStringItem(index = sys.maxint, label = col_val)
    1058
    1059                  # set data to be a copy of items
    1060                  self.data = items
    1061
    1062                  wx.EndBusyCursor()
    1063          #------------------------------------------------------------
    1064 -        def set_data(self, data=None):
    1065                  """<data> assumed to be a list corresponding to the item indices"""
    1066                  if data is not None:
    1067                          item_count = self.GetItemCount()
    1068                          if len(data) != item_count:
    1069                                  _log.debug('<data> length (%s) must be equal to number of list items (%s)  (%s, thread [%s])', len(data), item_count, self.debug, thread.get_ident())
    1070                          for item_idx in range(len(data)):
    1071                                  self.SetItemData(item_idx, item_idx)
    1072                  self.__data = data
    1073                  self.__tt_last_item = None
    1074                  # string data (rows/visible list items) not modified,
    1075                  # so no need to call _update_sorting_metadata
    1076                  return
    1077
    1078 -        def _get_data(self):
    1079                  # slower than "return self.__data" but helps with detecting
    1080                  # problems with len(__data)<>self.GetItemCount()
    1081                  return self.get_item_data()             # returns all data if item_idx is None
    1082
    1083          data = property(_get_data, set_data)
    1084          #------------------------------------------------------------
    1085 -        def set_selections(self, selections=None):
    1086                  self.Select(0, on = 0)
    1087                  if selections is None:
    1088                          return
    1089                  for idx in selections:
    1090                          self.Select(idx = idx, on = 1)
    1091
    1092 -        def __get_selections(self):
    1093                  if self.__is_single_selection:
    1094                          return [self.GetFirstSelected()]
    1095                  selections = []
    1096                  idx = self.GetFirstSelected()
    1097                  while idx != -1:
    1098                          selections.append(idx)
    1099                          idx = self.GetNextSelected(idx)
    1100                  return selections
    1101
    1102          selections = property(__get_selections, set_selections)
    1103          #------------------------------------------------------------
    1104          # getters
    1105          #------------------------------------------------------------
    1106 -        def get_column_labels(self):
    1107                  labels = []
    1108                  for col_idx in self.GetColumnCount():
    1109                          col = self.GetColumn(col = col_idx)
    1110                          labels.append(col.GetText())
    1111                  return labels
    1112          #------------------------------------------------------------
    1113 -        def get_item(self, item_idx=None):
    1114                  if item_idx is not None:
    1115                          return self.GetItem(item_idx)
    1116          #------------------------------------------------------------
    1117 -        def get_items(self):
    1118                  return [ self.GetItem(item_idx) for item_idx in range(self.GetItemCount()) ]
    1119          #------------------------------------------------------------
    1120 -        def get_string_items(self):
    1121                  return [ self.GetItemText(item_idx) for item_idx in range(self.GetItemCount()) ]
    1122          #------------------------------------------------------------
    1123 -        def get_selected_items(self, only_one=False):
    1124
    1125                  if self.__is_single_selection or only_one:
    1126                          return self.GetFirstSelected()
    1127
    1128                  items = []
    1129                  idx = self.GetFirstSelected()
    1130                  while idx != -1:
    1131                          items.append(idx)
    1132                          idx = self.GetNextSelected(idx)
    1133
    1134                  return items
    1135          #------------------------------------------------------------
    1136 -        def get_selected_string_items(self, only_one=False):
    1137
    1138                  if self.__is_single_selection or only_one:
    1139                          return self.GetItemText(self.GetFirstSelected())
    1140
    1141                  items = []
    1142                  idx = self.GetFirstSelected()
    1143                  while idx != -1:
    1144                          items.append(self.GetItemText(idx))
    1145                          idx = self.GetNextSelected(idx)
    1146
    1147                  return items
    1148          #------------------------------------------------------------
    1149 -        def get_item_data(self, item_idx = None):
    1150                  if self.__data is None: # this isn't entirely clean
    1151                          return None
    1152
    1153                  if item_idx is not None:
    1154                          return self.__data[self.map_item_idx2data_idx(item_idx)]
    1155
    1156                  # if <idx> is None return all data up to item_count,
    1157                  # in case of len(__data) <> self.GetItemCount() this
    1158                  # gives the chance to figure out what is going on
    1159                  return [ self.__data[self.map_item_idx2data_idx(item_idx)] for item_idx in range(self.GetItemCount()) ]
    1160          #------------------------------------------------------------
    1161 -        def get_selected_item_data(self, only_one=False):
    1162
    1163                  if self.__is_single_selection or only_one:
    1164                          if self.__data is None:
    1165                                  return None
    1166                          idx = self.GetFirstSelected()
    1167                          if idx == -1:
    1168                                  return None
    1169                          return self.__data[self.map_item_idx2data_idx(idx)]
    1170
    1171                  data = []
    1172                  if self.__data is None:
    1173                          return data
    1174                  idx = self.GetFirstSelected()
    1175                  while idx != -1:
    1176                          data.append(self.__data[self.map_item_idx2data_idx(idx)])
    1177                          idx = self.GetNextSelected(idx)
    1178
    1179                  return data
    1180          #------------------------------------------------------------
    1181 -        def deselect_selected_item(self):
    1182                  self.Select(idx = self.GetFirstSelected(), on = 0)
    1183          #------------------------------------------------------------
    1184 -        def remove_item(self, item_idx=None):
    1185                  # do NOT remove the corresponding data because even if
    1186                  # the item pointing to this data instance is gone all
    1187                  # other items will still point to their corresponding
    1188                  # *initial* row numbers
    1189                  #if self.__data is not None:
    1190                  #       del self.__data[self.map_item_idx2data_idx(item_idx)]
    1191                  self.DeleteItem(item_idx)
    1192                  self.__tt_last_item = None
    1193                  self._invalidate_sorting_metadata()
    1194          #------------------------------------------------------------
    1195          # event handlers
    1196          #------------------------------------------------------------
    1197 -        def _on_list_item_activated(self, event):
    1198                  event.Skip()
    1199                  if self.__activate_callback is not None:
    1200                          self.__activate_callback(event)
    1201          #------------------------------------------------------------
    1202 -        def _on_list_item_rightclicked(self, event):
    1203                  event.Skip()
    1204                  if self.__rightclick_callback is not None:
    1205                          self.__rightclick_callback(event)
    1206          #------------------------------------------------------------
    1207 -        def _on_char(self, evt):
    1208
    1209                  if evt.GetModifiers() != wx.MOD_CMD:
    1210                          evt.Skip()
    1211                          return
    1212
    1213                  if unichr(evt.GetRawKeyCode()) != u'f':
    1214                          evt.Skip()
    1215                          return
    1216
    1217                  if self.__search_dlg is not None:
    1218                          self.__search_dlg.Close()
    1219                          return
    1220
    1221                  if len(self.__searchable_cols) == 0:
    1222                          return
    1223
    1224                  if self.__search_data is None:
    1225                          self.__search_data = wx.FindReplaceData()
    1226                  self.__search_dlg = wx.FindReplaceDialog (
    1227                          self,
    1228                          self.__search_data,
    1229                          _('Search in list'),
    1230                          wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD
    1231                  )
    1232                  self.__search_dlg.Show(True)
    1233          #------------------------------------------------------------
    1234 -        def _on_mouse_motion(self, event):
    1235                  """Update tooltip on mouse motion.
    1236
    1237                          for s in dir(wx):
    1238                                  if s.startswith('LIST_HITTEST'):
    1239                                          print s, getattr(wx, s)
    1240
    1241                          LIST_HITTEST_ABOVE 1
    1242                          LIST_HITTEST_BELOW 2
    1243                          LIST_HITTEST_NOWHERE 4
    1244                          LIST_HITTEST_ONITEM 672
    1245                          LIST_HITTEST_ONITEMICON 32
    1246                          LIST_HITTEST_ONITEMLABEL 128
    1247                          LIST_HITTEST_ONITEMRIGHT 256
    1248                          LIST_HITTEST_ONITEMSTATEICON 512
    1249                          LIST_HITTEST_TOLEFT 1024
    1250                          LIST_HITTEST_TORIGHT 2048
    1251                  """
    1252                  item_idx, where_flag = self.HitTest(wx.Point(event.X, event.Y))
    1253
    1254                  # pointer on item related area at all ?
    1255                  if where_flag not in [
    1256                          wx.LIST_HITTEST_ONITEMLABEL,
    1257                          wx.LIST_HITTEST_ONITEMICON,
    1258                          wx.LIST_HITTEST_ONITEMSTATEICON,
    1259                          wx.LIST_HITTEST_ONITEMRIGHT,
    1260                          wx.LIST_HITTEST_ONITEM
    1261                  ]:
    1262                          self.__tt_last_item = None                                              # not on any item
    1263                          self.SetToolTipString(self.__tt_static_part)
    1264                          return
    1265
    1266                  # same item as last time around ?
    1267                  if self.__tt_last_item == item_idx:
    1268                          return
    1269
    1270                  # remeber the new item we are on
    1271                  self.__tt_last_item = item_idx
    1272
    1273                  # HitTest() can return -1 if it so pleases, meaning that no item
    1274                  # was hit or else that maybe there aren't any items (empty list)
    1275                  if item_idx == wx.NOT_FOUND:
    1276                          self.SetToolTipString(self.__tt_static_part)
    1277                          return
    1278
    1279                  # do we *have* item data ?
    1280                  if self.__data is None:
    1281                          self.SetToolTipString(self.__tt_static_part)
    1282                          return
    1283
    1284                  # under some circumstances the item_idx returned
    1285                  # by HitTest() may be out of bounds with respect to
    1286                  # self.__data, this hints at a sync problem between
    1287                  # setting display items and associated data
    1288                  if (
    1289                          (item_idx > (len(self.__data) - 1))
    1290                                  or
    1291                          (item_idx < -1)
    1292                  ):
    1293                          self.SetToolTipString(self.__tt_static_part)
    1294                          print "*************************************************************"
    1295                          print "GNUmed has detected an inconsistency with list item tooltips."
    1296                          print ""
    1297                          print "This is not a big problem and you can keep working."
    1298                          print ""
    1299                          print "However, please send us the following so we can fix GNUmed:"
    1300                          print ""
    1301                          print "item idx: %s" % item_idx
    1302                          print 'where flag: %s' % where_flag
    1303                          print 'data list length: %s' % len(self.__data)
    1304                          print "*************************************************************"
    1305                          return
    1306
    1307                  dyna_tt = None
    1308                  if self.__item_tooltip_callback is not None:
    1309                          dyna_tt = self.__item_tooltip_callback(self.__data[self.map_item_idx2data_idx(item_idx)])
    1310
    1311                  if dyna_tt is None:
    1312                          self.SetToolTipString(self.__tt_static_part)
    1313                          return
    1314
    1315                  self.SetToolTipString(dyna_tt)
    1316          #------------------------------------------------------------
    1317          # search related methods
    1318          #------------------------------------------------------------
    1319 -        def _on_search_dlg_closed(self, evt):
    1320                  self.__search_dlg.Destroy()
    1321                  self.__search_dlg = None
    1322          #------------------------------------------------------------
    1323 -        def _on_lost_focus(self, evt):
    1324                  evt.Skip()
    1325                  if self.__search_dlg is None:
    1326                          return
    1327  #               print self.FindFocus()
    1328  #               print self.__search_dlg
    1329                  #self.__search_dlg.Close()
    1330          #------------------------------------------------------------
    1331 -        def __on_search_match(self, search_term):
    1332                  for row_idx in range(self.__next_line_to_search, self.ItemCount):
    1333                          for col_idx in range(self.ColumnCount):
    1334                                  if col_idx not in self.__searchable_cols:
    1335                                          continue
    1336                                  col_val = self.GetItem(row_idx, col_idx).GetText()
    1337                                  if regex.search(search_term, col_val, regex.U | regex.I) is not None:
    1338                                          self.Select(row_idx)
    1339                                          self.EnsureVisible(row_idx)
    1340                                          if row_idx == self.ItemCount - 1:
    1341                                                  # wrap around
    1342                                                  self.__next_line_to_search = 0
    1343                                          else:
    1344                                                  self.__next_line_to_search = row_idx + 1
    1345                                          return True
    1346                  # wrap around
    1347                  self.__next_line_to_search = 0
    1348                  return False
    1349          #------------------------------------------------------------
    1350 -        def _on_search_first_match(self, evt):
    1351                  self.__on_search_match(evt.GetFindString())
    1352          #------------------------------------------------------------
    1353 -        def _on_search_next_match(self, evt):
    1354                  self.__on_search_match(evt.GetFindString())
    1355          #------------------------------------------------------------
    1356          # properties
    1357          #------------------------------------------------------------
    1358 -        def _get_activate_callback(self):
    1359                  return self.__activate_callback
    1360
    1361 -        def _set_activate_callback(self, callback):
    1362                  if callback is None:
    1363                          self.Unbind(wx.EVT_LIST_ITEM_ACTIVATED)
    1364                  else:
    1365                          if not callable(callback):
    1366                                  raise ValueError('<activate> callback is not a callable: %s' % callback)
    1367                          self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_list_item_activated)
    1368                  self.__activate_callback = callback
    1369
    1370          activate_callback = property(_get_activate_callback, _set_activate_callback)
    1371          #------------------------------------------------------------
    1372 -        def _get_rightclick_callback(self):
    1373                  return self.__rightclick_callback
    1374
    1375 -        def _set_rightclick_callback(self, callback):
    1376                  if callback is None:
    1377                          self.Unbind(wx.EVT_LIST_ITEM_RIGHT_CLICK)
    1378                  else:
    1379                          if not callable(callback):
    1380                                  raise ValueError('<rightclick> callback is not a callable: %s' % callback)
    1381                          self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._on_list_item_rightclicked)
    1382                  self.__rightclick_callback = callback
    1383
    1384          rightclick_callback = property(_get_rightclick_callback, _set_rightclick_callback)
    1385          #------------------------------------------------------------
    1386 -        def _set_item_tooltip_callback(self, callback):
    1387                  if callback is not None:
    1388                          if not callable(callback):
    1389                                  raise ValueError('<item_tooltip> callback is not a callable: %s' % callback)
    1390                  self.__item_tooltip_callback = callback
    1391
    1392          # the callback must be a function which takes a single argument
    1393          # the argument is the data for the item the tooltip is on
    1394          # the callback must return None if no item tooltip is to be shown
    1395          # otherwise it must return a string (possibly with \n)
    1396          item_tooltip_callback = property(lambda x:x, _set_item_tooltip_callback)
    1397          #------------------------------------------------------------
    1398 -        def _set_searchable_cols(self, cols):
    1399                  # zero-based list of which columns to search
    1400                  if cols is None:
    1401                          self.__searchable_cols = range(self.ColumnCount)
    1402                          return
    1403                  # weed out columns to be searched which
    1404                  # don't exist an uniquify them
    1405                  new_cols = {}
    1406                  for col in cols:
    1407                          if col < self.ColumnCount:
    1408                                  new_cols[col] = True
    1409                  self.__searchable_cols = new_cols.keys()
    1410
    1411          searchable_columns = property(lambda x:x, _set_searchable_cols)
    1412          #------------------------------------------------------------
    1413          # ColumnSorterMixin API
    1414          #------------------------------------------------------------
    1415 -        def GetListCtrl(self):
    1416                  if self.itemDataMap is None:
    1417                          self._update_sorting_metadata()
    1418                  return self                                                             # required
    1419          #------------------------------------------------------------
    1420 -        def OnSortOrderChanged(self):
    1421                  self._cleanup_column_headers()
    1422                  # annotate sort column
    1423                  col_idx, is_ascending = self.GetSortState()
    1424                  col_state = self.GetColumn(col_idx)
    1425                  col_state.m_text += self.sort_order_tags[is_ascending]
    1426                  self.SetColumn(col_idx, col_state)
    1427          #------------------------------------------------------------
    1428 -        def _generate_map_for_sorting(self):
    1429                  dict2sort = {}
    1430                  item_count = self.GetItemCount()
    1431                  if item_count == 0:
    1432                          return dict2sort
    1433                  col_count = self.GetColumnCount()
    1434                  for item_idx in range(item_count):
    1435                          dict2sort[item_idx] = ()
    1436                          if col_count == 0:
    1437                                  continue
    1438                          for col_idx in range(col_count):
    1439                                  dict2sort[item_idx] += (self.GetItem(item_idx, col_idx).GetText(), )
    1440
    1441                  return dict2sort
    1442          #------------------------------------------------------------
    1443 -        def _cleanup_column_headers(self):
    1444                  for col_idx in range(self.ColumnCount):
    1445                          col_state = self.GetColumn(col_idx)
    1446                          if col_state.m_text.endswith(self.sort_order_tags[True]):
    1447                                  col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[True])]
    1448                          if col_state.m_text.endswith(self.sort_order_tags[False]):
    1449                                  col_state.m_text = col_state.m_text[:-len(self.sort_order_tags[False])]
    1450                          self.SetColumn(col_idx, col_state)
    1451          #------------------------------------------------------------
    1452 -        def _invalidate_sorting_metadata(self):
    1453                  self.itemDataMap = None
    1454                  self.SetColumnCount(self.GetColumnCount())
    1455                  self._cleanup_column_headers()
    1456          #------------------------------------------------------------
    1457 -        def _update_sorting_metadata(self):
    1458                  self.itemDataMap = self._generate_map_for_sorting()
    1459          #------------------------------------------------------------
    1460 -        def _on_col_click(self, event):
    1461                  # for debugging:
    1462                  # print "column clicked : %s" % (event.GetColumn())
    1463                  # column, order = self.GetSortState()
    1464                  # print "column %s sort %s" % (column, order)
    1465                  # print self._colSortFlag
    1466                  # print self.itemDataMap
    1467                  event.Skip()
    1468
    1469  #================================================================
    1470  # main
    1471  #----------------------------------------------------------------
    1472  if __name__ == '__main__':
    1473
    1474          if len(sys.argv) < 2:
    1475                  sys.exit()
    1476
    1477          if sys.argv[1] != 'test':
    1478                  sys.exit()
    1479
    1480          sys.path.insert(0, '../../')
    1481
    1482          from Gnumed.pycommon import gmI18N
    1483          gmI18N.activate_locale()
    1484          gmI18N.install_domain()
    1485
    1486          #------------------------------------------------------------
    1487 -        def test_wxMultiChoiceDialog():
    1488                  app = wx.PyWidgetTester(size = (400, 500))
    1489                  dlg = wx.MultiChoiceDialog (
    1490                          parent = None,
    1491                          message = 'test message',
    1492                          caption = 'test caption',
    1493                          choices = ['a', 'b', 'c', 'd', 'e']
    1494                  )
    1495                  dlg.ShowModal()
    1496                  sels = dlg.GetSelections()
    1497                  print "selected:"
    1498                  for sel in sels:
    1499                          print sel
    1500          #------------------------------------------------------------
    1501 -        def test_get_choices_from_list():
    1502
    1503                  def edit(argument):
    1504                          print "editor called with:"
    1505                          print argument
    1506
    1507                  def refresh(lctrl):
    1508                          choices = ['a', 'b', 'c']
    1509                          lctrl.set_string_items(choices)
    1510
    1511                  app = wx.PyWidgetTester(size = (200, 50))
    1512                  chosen = get_choices_from_list (
    1513  #                       msg = 'select a health issue\nfrom the list below\n',
    1514                          caption = 'select health issues',
    1515                          #choices = [['D.M.II', '4'], ['MS', '3'], ['Fraktur', '2']],
    1516                          #columns = ['issue', 'no of episodes']
    1517                          columns = ['issue'],
    1518                          refresh_callback = refresh
    1519                          #, edit_callback = edit
    1520                  )
    1521                  print "chosen:"
    1522                  print chosen
    1523          #------------------------------------------------------------
    1524 -        def test_item_picker_dlg():
    1525                  app = wx.PyWidgetTester(size = (200, 50))
    1526                  dlg = cItemPickerDlg(None, -1, msg = 'Pick a few items:')
    1527                  dlg.set_columns(['Plugins'], ['Load in workplace', 'dummy'])
    1528                  #dlg.set_columns(['Plugins'], [])
    1529                  dlg.set_string_items(['patient', 'emr', 'docs'])
    1530                  result = dlg.ShowModal()
    1531                  print result
    1532                  print dlg.get_picks()
    1533          #------------------------------------------------------------
    1534          #test_get_choices_from_list()
    1535          #test_wxMultiChoiceDialog()
    1536          test_item_picker_dlg()
    1537
    1538  #================================================================
    1539  #
    1540