# Module `Gnumed.wxpython.gmImageWidgets`

GNUmed image display widgets.

## Classes

`class cImageDisplay (parent, ID, bitmap, **kwargs)`
  Expand source code

      class cImageDisplay(wx.lib.statbmp.GenStaticBitmap):
              """Control showing an image."""
              def __init__(self, parent, ID, bitmap, **kwargs):
                      self.__empty_bmp = wx.Bitmap(wx.Image(1, 1, True))
                      super().__init__(parent, ID, self.__empty_bmp, **kwargs)
                      self.__init_ui()
                      self.__filename = None
                      self.__zoom_count = 0
                      self.__min_width = 25
                      self.__current_rotation = 0             # in degrees
                      self.__callback_on_lclick = None
                      self.refresh()

              #--------------------------------------------------------
              def process_char(self, char) -> bool:
                      """Call action based on character.

                      Returns:
                              True/False based on whether the character mapped to a command.
                      """
                      match char:
                              case 'r':
                                      self.__current_rotation += 90
                                      if self.__current_rotation == 360:
                                              self.__current_rotation = 0
                                      self.refresh()
                              case 'l':
                                      self.__current_rotation -= 90
                                      if self.__current_rotation == -360:
                                              self.__current_rotation = 0
                                      self.refresh()
                              case 'o':
                                      self.__zoom_count = 0
                                      self.__current_rotation = 0
                                      self.refresh()
                              case '+':
                                      self.__zoom_count += 1
                                      self.refresh()
                              case '-':
                                      self.__zoom_count -= 1
                                      self.refresh()
                              case _:
                                      return False

                      return True

              #--------------------------------------------------------
              def process_keycode(self, keycode) -> bool:
                      """Call action based on translated key code.

                      Returns:
                              True/False based on whether the key code mapped to a command.
                      """
                      return False
      #               match keycode:
      #                       case 317: print('scroll down')
      #                       case 315: print('scroll up')
      #                       case 314: print('scroll left')
      #                       case 316: print('scroll right')
      #                       case _:
      #                               print('bmp/unmapped keycode:', keycode)
      #                               return False
      #
      #               return True

              #--------------------------------------------------------
              # event handlers
              #--------------------------------------------------------
              def _on_bitmap_leftclicked(self, event):
                      event.Skip()
                      self.SetFocus()
                      if not self.__callback_on_lclick:
                              return

                      self.__callback_on_lclick()

              #--------------------------------------------------------
              def _on_char(self, event):
                      uk = event.UnicodeKey
                      if uk != wx.WXK_NONE:
                              if not self.process_char(chr(uk)):
                                      event.Skip()
                              return

                      kc = event.KeyCode
                      if kc != wx.WXK_NONE:
                              self.process_keycode(kc)
                              if not self.process_keycode(kc):
                                      event.Skip()
                              return

                      event.Skip()

              #--------------------------------------------------------
              # properties
              #--------------------------------------------------------
              def __get_filename(self):
                      return self.__filename

              def __set_filename(self, filename:str):
                      if filename != self.__filename:
                              self.__zoom_count = 0
                              self.__current_rotation = 0
                      self.__filename = filename
                      self.refresh()

              filename = property(__get_filename, __set_filename)

              #--------------------------------------------------------
              # internal helpers
              #--------------------------------------------------------
              def __init_ui(self):
                      self.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
                      self.Bind(wx.EVT_CHAR, self._on_char)

              #--------------------------------------------------------
              def __adjust_bitmap(self):
                      img_data = None
                      bitmap = None
                      try:
                              img_data = wx.Image(self.__filename, wx.BITMAP_TYPE_ANY)
                              orig_width = img_data.GetWidth()
                      except Exception:
                              _log.exception('cannot load image from [%s]', self.__filename)
                              return False

                      if self.__zoom_count != 0:
                              zoom_step = orig_width // 10
                              target_width = orig_width + (zoom_step * self.__zoom_count)
                              target_width = max(target_width, self.__min_width)
                              try:
                                      orig_height = img_data.GetHeight()
                                      target_height = round((orig_height / orig_width) * target_width)
                                      if target_height and target_width:
                                              img_data.Rescale(target_width, target_height, quality = wx.IMAGE_QUALITY_HIGH)
                                      else:
                                              _log.debug('cannot resize image [%s] to %sx%s', self.__filename, target_width, target_height)
                              except Exception:
                                      _log.exception('cannot resize image [%s]', self.__filename)
                                      return False

                      try:
                              for cnt in range(abs(self.__current_rotation) // 90):
                                      img_data = img_data.Rotate90(clockwise = self.__current_rotation > 0)
                      except Exception:
                              _log.exception('cannot rotate image from [%s]', self.__filename)
                              return False

                      try:
                              bitmap = wx.Bitmap(img_data)
                      except Exception:
                              _log.exception('cannot create bitmap from image in [%s]', self.__filename)
                              return False

                      return bitmap

              #--------------------------------------------------------
              def refresh(self):
                      self.SetBitmap(self.__empty_bmp)
                      if not self.__filename:
                              return None

                      bitmap = self.__adjust_bitmap()
                      if not bitmap:
                              return False

                      self.SetBitmap(bitmap)
                      return True

  Control showing an image.

  Default class constructor.

  :param `parent`: parent window, must not be `None`; :param integer `ID`: window identifier. A value of -1 indicates a default value; :param wx.Bitmap `bitmap`: the static bitmap used in the control; :param `pos`: the control position. A value of (-1, -1) indicates a default position, chosen by either the windowing system or wxPython, depending on platform; :param `size`: the control size. A value of (-1, -1) indicates a default size, chosen by either the windowing system or wxPython, depending on platform; :param integer `style`: the underlying :class:`wx.Control` style; :param string `name`: the widget name.

  :type parent: :class:`wx.Window` :type pos: tuple or :class:`wx.Point` :type size: tuple or :class:`wx.Size`

  ### Ancestors

  - wx.lib.statbmp.GenStaticBitmap
  - wx._core.Control
  - wx._core.Window
  - wx._core.WindowBase
  - wx._core.EvtHandler
  - wx._core.Object
  - wx._core.Trackable
  - sip.wrapper
  - sip.simplewrapper

  ### Instance variables

  `prop filename`
    Expand source code

        def __get_filename(self):
                return self.__filename

  ### Methods

  `def process_char(self, char) ‑> bool`
    Expand source code

        def process_char(self, char) -> bool:
                """Call action based on character.

                Returns:
                        True/False based on whether the character mapped to a command.
                """
                match char:
                        case 'r':
                                self.__current_rotation += 90
                                if self.__current_rotation == 360:
                                        self.__current_rotation = 0
                                self.refresh()
                        case 'l':
                                self.__current_rotation -= 90
                                if self.__current_rotation == -360:
                                        self.__current_rotation = 0
                                self.refresh()
                        case 'o':
                                self.__zoom_count = 0
                                self.__current_rotation = 0
                                self.refresh()
                        case '+':
                                self.__zoom_count += 1
                                self.refresh()
                        case '-':
                                self.__zoom_count -= 1
                                self.refresh()
                        case _:
                                return False

                return True

    Call action based on character.

    ## Returns

    True/False based on whether the character mapped to a command.

  `def process_keycode(self, keycode) ‑> bool`
    Expand source code

        def process_keycode(self, keycode) -> bool:
                """Call action based on translated key code.

                Returns:
                        True/False based on whether the key code mapped to a command.
                """
                return False

    Call action based on translated key code.

    ## Returns

    True/False based on whether the key code mapped to a command.

  `def refresh(self)`
    Expand source code

        def refresh(self):
                self.SetBitmap(self.__empty_bmp)
                if not self.__filename:
                        return None

                bitmap = self.__adjust_bitmap()
                if not bitmap:
                        return False

                self.SetBitmap(bitmap)
                return True