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

# Source Code for Module Gnumed.wxpython.gmGuiHelpers

      1  """GNUmed GUI helper classes and functions.
      2
      3  This module provides some convenient wxPython GUI
      4  helper thingies that are widely used throughout
      5  GNUmed.
      6  """
      7  # ========================================================================
      8  __version__ = "$Revision: 1.106 $"
      9  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>"
     10  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     11
     12  import os
     13  import logging
     14  import sys
     15
     16
     17  import wx
     18
     19
     20  if __name__ == '__main__':
     21          sys.path.insert(0, '../../')
     22  from Gnumed.pycommon import gmMatchProvider
     23  from Gnumed.wxpython import gmPhraseWheel
     24
     25
     26  _log = logging.getLogger('gm.main')
     27  # ========================================================================
     28 -class cThreeValuedLogicPhraseWheel(gmPhraseWheel.cPhraseWheel):
     29
     30 -        def __init__(self, *args, **kwargs):
     31
     32                  gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
     33
     34                  items = [
     35                          {'list_label': _('Yes: + / ! / 1'), 'field_label': _('yes'), 'data': True, 'weight': 0},
     36                          {'list_label': _('No: - / 0'), 'field_label': _('no'), 'data': False, 'weight': 1},
     37                          {'list_label': _('Unknown: ?'), 'field_label': _('unknown'), 'data': None, 'weight': 2},
     38                  ]
     39                  mp = gmMatchProvider.cMatchProvider_FixedList(items)
     40                  mp.setThresholds(1, 1, 2)
     41                  mp.word_separators = '[ :/]+'
     42                  mp.word_separators = None
     43                  mp.ignored_chars = r"[.'\\(){}\[\]<>~#*$%^_=&@\t23456]+" + r'"'
     44
     45                  self.matcher = mp
     46  # ========================================================================
     47  from Gnumed.wxGladeWidgets import wxg2ButtonQuestionDlg
     48
     49 -class c2ButtonQuestionDlg(wxg2ButtonQuestionDlg.wxg2ButtonQuestionDlg):
     50
     51 -        def __init__(self, *args, **kwargs):
     52
     53                  caption = kwargs['caption']
     54                  question = kwargs['question']
     55                  button_defs = kwargs['button_defs'][:2]
     56                  del kwargs['caption']
     57                  del kwargs['question']
     58                  del kwargs['button_defs']
     59
     60                  try:
     61                          show_checkbox = kwargs['show_checkbox']
     62                          del kwargs['show_checkbox']
     63                  except KeyError:
     64                          show_checkbox = False
     65
     66                  try:
     67                          checkbox_msg = kwargs['checkbox_msg']
     68                          del kwargs['checkbox_msg']
     69                  except KeyError:
     70                          checkbox_msg = None
     71
     72                  try:
     73                          checkbox_tooltip = kwargs['checkbox_tooltip']
     74                          del kwargs['checkbox_tooltip']
     75                  except KeyError:
     76                          checkbox_tooltip = None
     77
     78                  wxg2ButtonQuestionDlg.wxg2ButtonQuestionDlg.__init__(self, *args, **kwargs)
     79
     80                  self.SetTitle(title = caption)
     81                  self._LBL_question.SetLabel(label = question)
     82
     83                  if not show_checkbox:
     84                          self._CHBOX_dont_ask_again.Hide()
     85                  else:
     86                          if checkbox_msg is not None:
     87                                  self._CHBOX_dont_ask_again.SetLabel(checkbox_msg)
     88                          if checkbox_tooltip is not None:
     89                                  self._CHBOX_dont_ask_again.SetToolTipString(checkbox_tooltip)
     90
     91                  buttons = [self._BTN_1, self._BTN_2]
     92                  for idx in range(len(button_defs)):
     93                          buttons[idx].SetLabel(label = button_defs[idx]['label'])
     94                          buttons[idx].SetToolTipString(button_defs[idx]['tooltip'])
     95                          try:
     96                                  if button_defs[idx]['default'] is True:
     97                                          buttons[idx].SetDefault()
     98                                          buttons[idx].SetFocus()
     99                          except KeyError:
    100                                  pass
    101
    102                  self.Fit()
    103          #--------------------------------------------------------
    104 -        def checkbox_is_checked(self):
    105                  return self._CHBOX_dont_ask_again.IsChecked()
    106          #--------------------------------------------------------
    107          # event handlers
    108          #--------------------------------------------------------
    109 -        def _on_BTN_1_pressed(self, evt):
    110                  if self.IsModal():
    111                          self.EndModal(wx.ID_YES)
    112                  else:
    113                          self.Close()
    114          #--------------------------------------------------------
    115 -        def _on_BTN_2_pressed(self, evt):
    116                  if self.IsModal():
    117                          self.EndModal(wx.ID_NO)
    118                  else:
    119                          self.Close()
    120  # ========================================================================
    121  from Gnumed.wxGladeWidgets import wxg3ButtonQuestionDlg
    122
    123 -class c3ButtonQuestionDlg(wxg3ButtonQuestionDlg.wxg3ButtonQuestionDlg):
    124
    125 -        def __init__(self, *args, **kwargs):
    126
    127                  caption = kwargs['caption']
    128                  question = kwargs['question']
    129                  button_defs = kwargs['button_defs'][:3]
    130
    131                  del kwargs['caption']
    132                  del kwargs['question']
    133                  del kwargs['button_defs']
    134
    135                  wxg3ButtonQuestionDlg.wxg3ButtonQuestionDlg.__init__(self, *args, **kwargs)
    136
    137                  self.SetTitle(title = caption)
    138                  self._LBL_question.SetLabel(label = question)
    139
    140                  buttons = [self._BTN_1, self._BTN_2, self._BTN_3]
    141                  for idx in range(len(button_defs)):
    142                          buttons[idx].SetLabel(label = button_defs[idx]['label'])
    143                          buttons[idx].SetToolTipString(button_defs[idx]['tooltip'])
    144                          try:
    145                                  if button_defs[idx]['default'] is True:
    146                                          buttons[idx].SetDefault()
    147                                          buttons[idx].SetFocus()
    148                          except KeyError:
    149                                  pass
    150
    151                  self.Fit()
    152          #--------------------------------------------------------
    153          # event handlers
    154          #--------------------------------------------------------
    155 -        def _on_BTN_1_pressed(self, evt):
    156                  if self.IsModal():
    157                          self.EndModal(wx.ID_YES)
    158                  else:
    159                          self.Close()
    160          #--------------------------------------------------------
    161 -        def _on_BTN_2_pressed(self, evt):
    162                  if self.IsModal():
    163                          self.EndModal(wx.ID_NO)
    164                  else:
    165                          self.Close()
    166  # ========================================================================
    167  from Gnumed.wxGladeWidgets import wxgMultilineTextEntryDlg
    168
    169 -class cMultilineTextEntryDlg(wxgMultilineTextEntryDlg.wxgMultilineTextEntryDlg):
    170          """Editor for a bit of text."""
    171
    172 -        def __init__(self, *args, **kwargs):
    173
    174                  try:
    175                          title = kwargs['title']
    176                          del kwargs['title']
    177                  except KeyError:
    178                          title = None
    179
    180                  try:
    181                          msg = kwargs['msg']
    182                          del kwargs['msg']
    183                  except KeyError:
    184                          msg = None
    185
    186                  try:
    187                          data = kwargs['data']
    188                          del kwargs['data']
    189                  except KeyError:
    190                          data = None
    191
    192                  try:
    193                          self.original_text = kwargs['text']
    194                          del kwargs['text']
    195                  except KeyError:
    196                          self.original_text = None
    197
    198                  wxgMultilineTextEntryDlg.wxgMultilineTextEntryDlg.__init__(self, *args, **kwargs)
    199
    200                  if title is not None:
    201                          self.SetTitle(title)
    202
    203                  if self.original_text is not None:
    204                          self._TCTRL_text.SetValue(self.original_text)
    205                          self._BTN_restore.Enable(True)
    206
    207                  if msg is None:
    208                          self._LBL_msg.Hide()
    209                  else:
    210                          self._LBL_msg.SetLabel(msg)
    211                          self.Layout()
    212                          self.Refresh()
    213
    214                  if data is None:
    215                          self._TCTRL_data.Hide()
    216                  else:
    217                          self._TCTRL_data.SetValue(data)
    218                          self.Layout()
    219                          self.Refresh()
    220          #--------------------------------------------------------
    221          # properties
    222          #--------------------------------------------------------
    223 -        def _get_value(self):
    224                  return self._TCTRL_text.GetValue()
    225
    226          value = property(_get_value, lambda x:x)
    227          #--------------------------------------------------------
    228 -        def _get_is_user_formatted(self):
    229                  return self._CHBOX_is_already_formatted.IsChecked()
    230
    231          is_user_formatted = property(_get_is_user_formatted, lambda x:x)
    232          #--------------------------------------------------------
    233 -        def _set_enable_user_formatting(self, value):
    234                  self._CHBOX_is_already_formatted.Enable(value)
    235
    236          enable_user_formatting = property(lambda x:x, _set_enable_user_formatting)
    237          #--------------------------------------------------------
    238          # event handlers
    239          #--------------------------------------------------------
    240 -        def _on_save_button_pressed(self, evt):
    241
    242                  if self.IsModal():
    243                          self.EndModal(wx.ID_SAVE)
    244                  else:
    245                          self.Close()
    246          #--------------------------------------------------------
    247 -        def _on_clear_button_pressed(self, evt):
    248                  self._TCTRL_text.SetValue(u'')
    249          #--------------------------------------------------------
    250 -        def _on_restore_button_pressed(self, evt):
    251                  if self.original_text is not None:
    252                          self._TCTRL_text.SetValue(self.original_text)
    253  # ========================================================================
    254  from Gnumed.business import gmSurgery
    255  from Gnumed.wxGladeWidgets import wxgGreetingEditorDlg
    256
    257 -class cGreetingEditorDlg(wxgGreetingEditorDlg.wxgGreetingEditorDlg):
    258
    259 -        def __init__(self, *args, **kwargs):
    260                  wxgGreetingEditorDlg.wxgGreetingEditorDlg.__init__(self, *args, **kwargs)
    261
    262                  self.surgery = gmSurgery.gmCurrentPractice()
    263                  self._TCTRL_message.SetValue(self.surgery.db_logon_banner)
    264          #--------------------------------------------------------
    265          # event handlers
    266          #--------------------------------------------------------
    267 -        def _on_save_button_pressed(self, evt):
    268                  self.surgery.db_logon_banner = self._TCTRL_message.GetValue().strip()
    269                  if self.IsModal():
    270                          self.EndModal(wx.ID_SAVE)
    271                  else:
    272                          self.Close()
    273  # ========================================================================
    274 -class cTreeExpansionHistoryMixin:
    275          """TreeCtrl mixin class to record expansion history."""
    276 -        def __init__(self):
    277                  if not isinstance(self, wx.TreeCtrl):
    278                          raise TypeError('[%s]: mixin can only be applied to wx.TreeCtrl, not [%s]' % (cTreeExpansionHistoryMixin, self.__class__.__name__))
    279                  self.expansion_state = {}
    280          #--------------------------------------------------------
    281          # public API
    282          #--------------------------------------------------------
    283 -        def snapshot_expansion(self):
    284                  self.__record_subtree_expansion(start_node_id = self.GetRootItem())
    285          #--------------------------------------------------------
    286 -        def restore_expansion(self):
    287                  if len(self.expansion_state) == 0:
    288                          return True
    289                  self.__restore_subtree_expansion(start_node_id = self.GetRootItem())
    290          #--------------------------------------------------------
    291 -        def print_expansion(self):
    292                  if len(self.expansion_state) == 0:
    293                          print "currently no expansion snapshot available"
    294                          return True
    295                  print "last snapshot of state of expansion"
    296                  print "-----------------------------------"
    297                  print "listing expanded nodes:"
    298                  for node_id in self.expansion_state.keys():
    299                          print "node ID:", node_id
    300                          print "  selected:", self.expansion_state[node_id]
    301          #--------------------------------------------------------
    302          # internal API
    303          #--------------------------------------------------------
    304 -        def __record_subtree_expansion(self, start_node_id=None):
    305                  """This records node expansion states based on the item label.
    306
    307                  A side effect of this is that identically named items can
    308                  become unduly synchronized in their expand state after a
    309                  snapshot/restore cycle.
    310
    311                  Better choices might be
    312
    313                          id(item.GetPyData()) or
    314                          item.GetPyData().get_tree_uid()
    315
    316                  where get_tree_uid():
    317
    318                          '[%s:%s]' % (self.__class__.__name__, id(self))
    319
    320                  or some such. This would survive renaming of the item.
    321
    322                  For database items it may be useful to include the
    323                  primary key which would - contrary to id() - survive
    324                  reloads from the database.
    325                  """
    326                  # protect against empty tree where not even
    327                  # a root node exists
    328                  if not start_node_id.IsOk():
    329                          return True
    330
    331                  if not self.IsExpanded(start_node_id):
    332                          return True
    333
    334                  self.expansion_state[self.GetItemText(start_node_id)] = self.IsSelected(start_node_id)
    335
    336                  child_id, cookie = self.GetFirstChild(start_node_id)
    337                  while child_id.IsOk():
    338                          self.__record_subtree_expansion(start_node_id = child_id)
    339                          child_id, cookie = self.GetNextChild(start_node_id, cookie)
    340
    341                  return
    342          #--------------------------------------------------------
    343 -        def __restore_subtree_expansion(self, start_node_id=None):
    344                  start_node_label = self.GetItemText(start_node_id)
    345                  try:
    346                          node_selected = self.expansion_state[start_node_label]
    347                  except KeyError:
    348                          return
    349
    350                  self.Expand(start_node_id)
    351                  if node_selected:
    352                          self.SelectItem(start_node_id)
    353
    354                  child_id, cookie = self.GetFirstChild(start_node_id)
    355                  while child_id.IsOk():
    356                          self.__restore_subtree_expansion(start_node_id = child_id)
    357                          child_id, cookie = self.GetNextChild(start_node_id, cookie)
    358
    359                  return
    360  # ========================================================================
    361 -class cFileDropTarget(wx.FileDropTarget):
    362          """Generic file drop target class.
    363
    364          Protocol:
    365                  Widgets being declared file drop targets
    366                  must provide the method:
    367
    368                          add_filenames(filenames)
    369          """
    370          #-----------------------------------------------
    371 -        def __init__(self, target):
    372                  wx.FileDropTarget.__init__(self)
    373                  self.target = target
    374                  _log.debug('setting up [%s] as file drop target', target)
    375          #-----------------------------------------------
    376 -        def OnDropFiles(self, x, y, filenames):
    377                  self.target.add_filenames(filenames)
    378  # ========================================================================
    379 -def file2scaled_image(filename=None, height=100):
    380          img_data = None
    381          bitmap = None
    382          rescaled_height = height
    383          try:
    384                  img_data = wx.Image(filename, wx.BITMAP_TYPE_ANY)
    385                  current_width = img_data.GetWidth()
    386                  current_height = img_data.GetHeight()
    387  #               if current_width == 0:
    388  #                       current_width = 1
    389  #               if current_height == 0:
    390  #                       current_height = 1
    391                  rescaled_width = (float(current_width) / current_height) * rescaled_height
    392                  img_data.Rescale(rescaled_width, rescaled_height, quality = wx.IMAGE_QUALITY_HIGH)              # w, h
    393                  bitmap = wx.BitmapFromImage(img_data)
    394                  del img_data
    395          except StandardError:
    396                  _log.exception('cannot load image from [%s]', filename)
    397                  del img_data
    398                  del bitmap
    399                  return None
    400
    401          return bitmap
    402  # ========================================================================
    403 -def gm_show_error(aMessage = None, aTitle = None):
    404          if aMessage is None:
    405                  aMessage = _('programmer forgot to specify error message')
    406
    407          aMessage += _("\n\nPlease consult the error log for all the gory details !")
    408
    409          if aTitle is None:
    410                  aTitle = _('generic error message')
    411
    412          dlg = wx.MessageDialog (
    413                  parent = None,
    414                  message = aMessage,
    415                  caption = aTitle,
    416                  style = wx.OK | wx.ICON_ERROR | wx.STAY_ON_TOP
    417          )
    418          dlg.ShowModal()
    419          dlg.Destroy()
    420          return True
    421  #-------------------------------------------------------------------------
    422 -def gm_show_info(aMessage = None, aTitle = None):
    423          if aMessage is None:
    424                  aMessage = _('programmer forgot to specify info message')
    425
    426          if aTitle is None:
    427                  aTitle = _('generic info message')
    428
    429          dlg = wx.MessageDialog (
    430                  parent = None,
    431                  message = aMessage,
    432                  caption = aTitle,
    433                  style = wx.OK | wx.ICON_INFORMATION | wx.STAY_ON_TOP
    434          )
    435          dlg.ShowModal()
    436          dlg.Destroy()
    437          return True
    438  #-------------------------------------------------------------------------
    439 -def gm_show_warning(aMessage=None, aTitle=None):
    440          if aMessage is None:
    441                  aMessage = _('programmer forgot to specify warning')
    442
    443          if aTitle is None:
    444                  aTitle = _('generic warning message')
    445
    446          dlg = wx.MessageDialog (
    447                  parent = None,
    448                  message = aMessage,
    449                  caption = aTitle,
    450                  style = wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP
    451          )
    452          dlg.ShowModal()
    453          dlg.Destroy()
    454          return True
    455  #-------------------------------------------------------------------------
    456 -def gm_show_question(aMessage='programmer forgot to specify question', aTitle='generic user question dialog', cancel_button=False, question=None, title=None):
    457          if cancel_button:
    458                  style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION | wx.STAY_ON_TOP
    459          else:
    460                  style = wx.YES_NO | wx.ICON_QUESTION | wx.STAY_ON_TOP
    461
    462          if question is None:
    463                  question = aMessage
    464          if title is None:
    465                  title = aTitle
    466
    467          dlg = wx.MessageDialog(None, question, title, style)
    468          btn_pressed = dlg.ShowModal()
    469          dlg.Destroy()
    470
    471          if btn_pressed == wx.ID_YES:
    472                  return True
    473          elif btn_pressed == wx.ID_NO:
    474                  return False
    475          else:
    476                  return None
    477  #======================================================================
    478  if __name__ == '__main__':
    479
    480          if len(sys.argv) < 2:
    481                  sys.exit()
    482
    483          if sys.argv[1] != 'test':
    484                  sys.exit()
    485
    486          from Gnumed.pycommon import gmI18N
    487          gmI18N.activate_locale()
    488          gmI18N.install_domain(domain='gnumed')
    489
    490          #------------------------------------------------------------------
    491 -        def test_scale_img():
    492                  app = wx.App()
    493                  img = file2scaled_image(filename = sys.argv[2])
    494                  print img
    495                  print img.Height
    496                  print img.Width
    497          #------------------------------------------------------------------
    498 -        def test_sql_logic_prw():
    499                  app = wx.PyWidgetTester(size = (200, 50))
    500                  prw = cThreeValuedLogicPhraseWheel(parent = app.frame, id = -1)
    501                  app.frame.Show(True)
    502                  app.MainLoop()
    503
    504                  return True
    505          #------------------------------------------------------------------
    506          #test_scale_img()
    507          test_sql_logic_prw()
    508
    509  #======================================================================
    510