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

# Source Code for Module Gnumed.wxpython.gmListWidgets

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