Gnumed.wxpython.gmListWidgets

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