view libgui/graphics/Figure.cc @ 29992:6ea4a84df9c7

rename QtHandles namespace to octave To simplify the codebase, replace QtHandles top-level namespace by octave. Since the header files for the Qt graphics code are not installed, this change shouldn't cause trouble for any external packages so we shouldn't need to provide an alias, even temporarily. Modified files: BaseControl.cc, BaseControl.h, ButtonControl.cc, ButtonControl.h, ButtonGroup.cc, ButtonGroup.h, Canvas.cc, Canvas.h, CheckBoxControl.cc, CheckBoxControl.h, Container.cc, Container.h, ContextMenu.cc, ContextMenu.h, EditControl.cc, EditControl.h, Figure.cc, Figure.h, FigureWindow.cc, FigureWindow.h, GLCanvas.cc, GLCanvas.h, GenericEventNotify.h, KeyMap.cc, KeyMap.h, ListBoxControl.cc, ListBoxControl.h, Logger.cc, Logger.h, Menu.cc, Menu.h, MenuContainer.h, Object.cc, Object.h, ObjectProxy.cc, ObjectProxy.h, Panel.cc, Panel.h, PopupMenuControl.cc, PopupMenuControl.h, PushButtonControl.cc, PushButtonControl.h, PushTool.cc, PushTool.h, QtHandlesUtils.cc, QtHandlesUtils.h, RadioButtonControl.cc, RadioButtonControl.h, SliderControl.cc, SliderControl.h, Table.cc, Table.h, TextControl.cc, TextControl.h, TextEdit.cc, TextEdit.h, ToggleButtonControl.cc, ToggleButtonControl.h, ToggleTool.cc, ToggleTool.h, ToolBar.cc, ToolBar.h, ToolBarButton.cc, ToolBarButton.h, annotation-dialog.cc, qt-graphics-toolkit.cc, qt-graphics-toolkit.h, and graphics-init.cc.
author John W. Eaton <jwe@octave.org>
date Wed, 18 Aug 2021 00:21:26 -0400
parents 0dd2742601e4
children d4d83344d653
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2011-2021 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <QAction>
#include <QActionEvent>
#include <QApplication>
#include <QClipboard>
#include <QEvent>
#include <QFileDialog>
#include <QFileInfo>
#include <QFrame>
#include <QImage>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QtDebug>
#include <QTimer>
#include <QToolBar>
#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
#  include <QWindow>
#  include <QScreen>
#endif

#include "Canvas.h"
#include "Container.h"
#include "Figure.h"
#include "FigureWindow.h"
#include "QtHandlesUtils.h"

#include "gui-preferences-global.h"
#include "qt-interpreter-events.h"

#include "file-ops.h"
#include "unwind-prot.h"
#include "utils.h"
#include "version.h"

#include "builtin-defun-decls.h"
#include "interpreter.h"

namespace octave
{

  DECLARE_GENERICEVENTNOTIFY_SENDER(MenuBar, QMenuBar);

  static QRect
  boundingBoxToRect (const Matrix& bb)
  {
    QRect r;

    if (bb.numel () == 4)
      {
        r = QRect (octave::math::round (bb(0)), octave::math::round (bb(1)),
                   octave::math::round (bb(2)), octave::math::round (bb(3)));
        if (! r.isValid ())
          r = QRect ();
      }

    return r;
  }

  static QImage
  pointer_to_qimage (const Matrix& cdata)
  {
    QImage retval (cdata.rows (), cdata.columns (), QImage::Format_ARGB32);
    QColor tmp ("White");
    QColor black ("Black");
    QColor white ("White");
    for (octave_idx_type ii = 0; ii < cdata.rows (); ii++)
      for (octave_idx_type jj = 0; jj < cdata.columns (); jj++)
        {
          if (cdata(ii,jj) == 1.0)
            tmp = black;
          else if (cdata(ii,jj) == 2.0)
            tmp = white;
          else
            tmp.setAlpha (0);

          retval.setPixel (jj, ii, tmp.rgba ());
        }

    return retval;
  }

  Figure*
  Figure::create (octave::base_qobject& oct_qobj, octave::interpreter& interp,
                  const graphics_object& go)
  {
    return new Figure (oct_qobj, interp, go, new FigureWindow ());
  }

  Figure::Figure (octave::base_qobject& oct_qobj, octave::interpreter& interp,
                  const graphics_object& go, FigureWindow *win)
    : Object (oct_qobj, interp, go, win), m_blockUpdates (false),
      m_figureToolBar (nullptr), m_menuBar (nullptr), m_innerRect (),
      m_outerRect (), m_previousHeight (0), m_resizable (true)
  {
    m_container = new Container (win, oct_qobj, interp);
    win->setCentralWidget (m_container);

    connect (m_container, QOverload<const octave::fcn_callback&>::of (&Container::interpreter_event),
             this, QOverload<const octave::fcn_callback&>::of (&Figure::interpreter_event));

    connect (m_container, QOverload<const octave::meth_callback&>::of (&Container::interpreter_event),
             this, QOverload<const octave::meth_callback&>::of (&Figure::interpreter_event));

    figure::properties& fp = properties<figure> ();

    // Adjust figure position
    m_innerRect = boundingBoxToRect (fp.get_boundingbox (true));
    m_outerRect = boundingBoxToRect (fp.get_boundingbox (false));

    set_geometry (m_innerRect);

    // Menubar
    m_menuBar = new MenuBar (win);
    win->setMenuBar (m_menuBar);
    m_menuBar->addReceiver (this);
    m_menuBar->setStyleSheet (m_menuBar->styleSheet () + global_menubar_style);


    // Status bar
    m_statusBar = win->statusBar ();
    m_statusBar->setVisible (false);

    if (fp.toolbar_is ("figure")
        || (fp.toolbar_is ("auto") && fp.menubar_is ("figure")))
      showFigureStatusBar (true);

    // Enable mouse tracking unconditionally
    enableMouseTracking ();

    // When this constructor gets called all properties are already
    // set, even non default.  We force "update" here to get things right.

    // Figure title
    update (figure::properties::ID_NUMBERTITLE);

    // Decide what keyboard events we listen to
    m_container->canvas (m_handle)->setEventMask (0);
    update (figure::properties::ID_KEYPRESSFCN);
    update (figure::properties::ID_KEYRELEASEFCN);

    // modal style
    update (figure::properties::ID_WINDOWSTYLE);

    // Handle resizing constraints
    update (figure::properties::ID_RESIZE);

    // Custom pointer data
    update (figure::properties::ID_POINTERSHAPECDATA);

    // Visibility
    update (figure::properties::ID_VISIBLE);

    connect (this, &Figure::asyncUpdate, this, &Figure::updateContainer);

    // Register for the signal that indicates when a window has moved
    // to a different screen
    connect (win, &FigureWindow::figureWindowShown,
             this, &Figure::figureWindowShown);

    win->addReceiver (this);
    m_container->addReceiver (this);
  }

  Figure::~Figure (void)
  { }

  QString
  Figure::fileName (void)
  {
    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    const figure::properties& fp = properties<figure> ();

    std::string name = fp.get_filename ();

    return QString::fromStdString (name);
  }

  void
  Figure::setFileName (const QString& name)
  {
    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    figure::properties& fp = properties<figure> ();

    fp.set_filename (name.toStdString ());
  }

  MouseMode
  Figure::mouseMode (void)
  {
    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    const figure::properties& fp = properties<figure> ();

    std::string mode = fp.get___mouse_mode__ ();

    if (mode == "zoom")
      {
        octave_scalar_map zm = fp.get___zoom_mode__ ().scalar_map_value ();

        std::string direction = zm.getfield ("Direction").string_value ();

        mode += ' ' + direction;
      }

    if (mode == "rotate")
      return RotateMode;
    else if (mode == "zoom in")
      return ZoomInMode;
    else if (mode == "zoom out")
      return ZoomOutMode;
    else if (mode == "pan")
      return PanMode;
    else if (mode == "text")
      return TextMode;

    return NoMode;
  }

  void
  Figure::set_geometry (QRect r)
  {
    QMainWindow *win = qWidget<QMainWindow> ();

    if (! m_resizable)
      {
        win->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred);
        win->setFixedSize (QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
      }

    // Unlock window if it is maximized or full-screen
    int state = win->windowState ();
    if (state == Qt::WindowFullScreen || state == Qt::WindowMaximized)
      win->setWindowState (Qt::WindowNoState);

    win->setGeometry (r);

    if (! m_resizable)
      {
        win->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
        win->setFixedSize(win->size ());
      }
  }

  Container*
  Figure::innerContainer (void)
  {
    return m_container;
  }

  void
  Figure::redraw (void)
  {
    Canvas *canvas = m_container->canvas (m_handle);

    if (canvas)
      canvas->redraw ();

    for (auto *qobj : qWidget<QWidget> ()->findChildren<QObject*> ())
      {
        if (qobj->objectName () == "UIPanel"
            || qobj->objectName () == "UIButtonGroup"
            || qobj->objectName () == "UIControl"
            || qobj->objectName () == "UITable")
          {
            Object *obj = Object::fromQObject (qobj);

            if (obj)
              obj->slotRedraw ();
          }
      }
  }

  void
  Figure::show (void)
  {
    QWidget *win = qWidget<QWidget> ();

    win->activateWindow ();
    win->raise ();
  }

  void
  Figure::print (const QString& file_cmd, const QString& term)
  {
    Canvas *canvas = m_container->canvas (m_handle);

    if (canvas)
      canvas->print (file_cmd, term);
  }

  uint8NDArray
  Figure::slotGetPixels (void)
  {
    uint8NDArray retval;
    Canvas *canvas = m_container->canvas (m_handle);

    if (canvas)
      {
        gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

        gh_mgr.process_events ();
        octave::autolock guard (gh_mgr.graphics_lock ());
        retval = canvas->getPixels ();
      }

    return retval;
  }

  void
  Figure::beingDeleted (void)
  {
    Canvas *canvas = m_container->canvas (m_handle.value (), false);

    if (canvas)
      canvas->blockRedraw (true);

    m_container->removeReceiver (this);
    qWidget<FigureWindow> ()->removeReceiver (this);
  }

  void
  Figure::update (int pId)
  {
    if (m_blockUpdates)
      return;

    figure::properties& fp = properties<figure> ();

    if (fp.is___printing__ ())
      return;

    QMainWindow *win = qWidget<QMainWindow> ();

    // If the window doesn't exist, there's nothing we can do.
    if (! win)
      return;

    m_blockUpdates = true;

    switch (pId)
      {
      case figure::properties::ID_POSITION:
        {
          m_innerRect = boundingBoxToRect (fp.get_boundingbox (true));
          int toffset = 0;
          int boffset = 0;

          for (auto *tb : win->findChildren<QToolBar*> ())
            if (! tb->isHidden ())
              toffset += tb->sizeHint ().height ();

          if (! m_menuBar->isHidden ())
            toffset += m_menuBar->sizeHint ().height ();

          if (! m_statusBar->isHidden ())
            boffset += m_statusBar->sizeHint ().height ();

          set_geometry (m_innerRect.adjusted (0, -toffset, 0, boffset));
        }
        break;

      case figure::properties::ID_NAME:
      case figure::properties::ID_NUMBERTITLE:
        win->setWindowTitle (Utils::fromStdString (fp.get_title ()));
        break;

      case figure::properties::ID_VISIBLE:
        if (fp.is_visible ())
          {
            QTimer::singleShot (0, win, &QMainWindow::show);
            if (! fp.is___gl_window__ ())
              {
                gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

                octave::autolock guard (gh_mgr.graphics_lock ());
                fp.set ("__gl_window__", "on");
              }
          }
        else
          win->hide ();
        break;

      case figure::properties::ID_RESIZE:
        if (fp.is_resize ())
          {
            win->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred);
            win->setFixedSize (QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
            m_resizable = true;
          }
        else
          {
            win->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
            win->setFixedSize(win->size ());
            m_resizable = false;
          }
        break;

      case figure::properties::ID_MENUBAR:
      case figure::properties::ID_TOOLBAR:
        if (fp.toolbar_is ("none"))
          showFigureStatusBar (false);
        else if (fp.toolbar_is ("figure"))
          showFigureStatusBar (true);
        else  // "auto"
          showFigureStatusBar (fp.menubar_is ("figure"));
        break;

      case figure::properties::ID_KEYPRESSFCN:
        if (fp.get_keypressfcn ().isempty ())
          m_container->canvas (m_handle)->clearEventMask (Canvas::KeyPress);
        else
          m_container->canvas (m_handle)->addEventMask (Canvas::KeyPress);
        // Signal the change to uipanels as well
        for (auto *qobj : qWidget<QWidget> ()->findChildren<QObject*> ())
          {
            if (qobj->objectName () == "UIPanel")
              {
                Object *obj = Object::fromQObject (qobj);

                if (obj)
                  {
                    if (fp.get_keypressfcn ().isempty ())
                      obj->innerContainer ()->canvas (m_handle)->
                        clearEventMask (Canvas::KeyPress);
                    else
                      obj->innerContainer ()->canvas (m_handle)->
                        addEventMask (Canvas::KeyPress);
                  }
              }
          }
        break;

      case figure::properties::ID_KEYRELEASEFCN:
        if (fp.get_keyreleasefcn ().isempty ())
          m_container->canvas (m_handle)->clearEventMask (Canvas::KeyRelease);
        else
          m_container->canvas (m_handle)->addEventMask (Canvas::KeyRelease);
        break;
        // Signal the change to uipanels as well
        for (auto *qobj : qWidget<QWidget> ()->findChildren<QObject*> ())
          {
            if (qobj->objectName () == "UIPanel")
              {
                Object *obj = Object::fromQObject (qobj);

                if (obj)
                  {
                    if (fp.get_keypressfcn ().isempty ())
                      obj->innerContainer ()->canvas (m_handle)->
                        clearEventMask (Canvas::KeyRelease);
                    else
                      obj->innerContainer ()->canvas (m_handle)->
                        addEventMask (Canvas::KeyRelease);
                  }
              }
          }
        break;

      case figure::properties::ID_WINDOWSTYLE:
        if (fp.windowstyle_is ("modal"))
          {
            bool is_visible = win->isVisible ();

            // if window is already visible, need to hide and reshow it in order to
            // make it use the modal settings
            if (is_visible)
              win->setVisible (false);

            win->setWindowModality (Qt::ApplicationModal);
            win->setVisible (is_visible);
          }
        else
          win->setWindowModality (Qt::NonModal);

        break;

      case figure::properties::ID_POINTERSHAPECDATA:
        m_pointer_cdata =
          pointer_to_qimage (fp.get_pointershapecdata ().matrix_value ());
        if (fp.get_pointer () != "custom")
          break;
        OCTAVE_FALLTHROUGH;

      case figure::properties::ID_POINTER:
      case figure::properties::ID_POINTERSHAPEHOTSPOT:
      case figure::properties::ID___MOUSE_MODE__:
      case figure::properties::ID___ZOOM_MODE__:
        m_container->canvas (m_handle)->setCursor (mouseMode (),
                                                   fp.get_pointer (),
                                                   m_pointer_cdata,
                                                   fp.get_pointershapehotspot ()
                                                   .matrix_value());
        break;

      default:
        break;
      }

    m_blockUpdates = false;
  }

  void
  Figure::showFigureStatusBar (bool visible)
  {
    if (m_statusBar
        && (! m_statusBar->isHidden ()) != visible)
      {
        int dy = m_statusBar->sizeHint ().height ();
        QRect r = qWidget<QWidget> ()->geometry ();

        if (! visible)
          r.adjust (0, 0, 0, -dy);
        else
          r.adjust (0, 0, 0, dy);

        m_blockUpdates = true;
        set_geometry (r);
        m_statusBar->setVisible (visible);
        m_blockUpdates = false;

        updateBoundingBox (false);
      }
  }

  void
  Figure::updateFigureHeight (int dh)
  {
    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());
    graphics_object go = object ();

    if (go.valid_object () && dh != 0)
      {
        QRect r = qWidget<QWidget> ()->geometry ();

        r.adjust (0, dh, 0, 0);

        m_blockUpdates = true;
        set_geometry (r);
        m_blockUpdates = false;

        updateBoundingBox (false);
      }
  }

  void
  Figure::updateStatusBar (ColumnVector pt)
  {
    if (! m_statusBar->isHidden ())
      m_statusBar->showMessage (QString ("(%1, %2)")
                                .arg (pt(0), 0, 'g', 5)
                                .arg (pt(1), 0, 'g', 5));
  }

  void
  Figure::do_connections (const QObject *receiver, const QObject* /* emitter */)
  {
    Object::do_connections (receiver);
    Object::do_connections (receiver, m_container->canvas (m_handle));
  }

  QWidget*
  Figure::menu (void)
  {
    return qWidget<QMainWindow> ()->menuBar ();
  }

  void
  Figure::updateBoundingBox (bool internal, int flags)
  {
    QWidget *win = qWidget<QWidget> ();
    Matrix bb (1, 4);
    std::string prop;

    if (internal)
      {
        prop = "position";
        QRect r = m_innerRect;

        if (flags & UpdateBoundingBoxPosition)
          r.moveTopLeft (win->mapToGlobal (m_container->pos ()));
        if (flags & UpdateBoundingBoxSize)
          r.setSize (m_container->size ());

        if (r.isValid () && r != m_innerRect)
          {
            m_innerRect = r;

            bb(0) = r.x ();
            bb(1) = r.y ();
            bb(2) = r.width ();
            bb(3) = r.height ();
          }
        else
          return;
      }
    else
      {
        prop = "outerposition";
        QRect r = m_outerRect;

        if (flags & UpdateBoundingBoxPosition)
          r.moveTopLeft (win->pos ());
        if (flags & UpdateBoundingBoxSize)
          r.setSize (win->frameGeometry ().size ());

        if (r.isValid () && r != m_outerRect)
          {
            m_outerRect = r;

            bb(0) = r.x ();
            bb(1) = r.y ();
            bb(2) = r.width ();
            bb(3) = r.height ();
          }
        else
          return;
      }

    figure::properties& fp = properties<figure> ();

    emit gh_set_event (m_handle, prop, fp.bbox2position (bb), false,
                       prop == "position");
  }

  bool
  Figure::eventNotifyBefore (QObject *obj, QEvent *xevent)
  {
    if (! m_blockUpdates)
      {
        // Clicking the toolbar or the menubar makes this figure current
        if (xevent->type () == QEvent::MouseButtonPress)
          {
            figure::properties& fp = properties<figure> ();

            gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

            graphics_object root = gh_mgr.get_object (0);

            if (fp.get_handlevisibility () == "on")
              root.set ("currentfigure",
                        fp.get___myhandle__ ().as_octave_value ());
          }

        if (obj == m_container)
          {
            // Do nothing...
          }
        else if (obj == m_menuBar)
          {
            switch (xevent->type ())
              {
              case QEvent::ActionAdded:
              case QEvent::ActionChanged:
              case QEvent::ActionRemoved:
                m_previousHeight = m_menuBar->sizeHint ().height ();

              default:
                break;
              }
          }
        else
          {
            switch (xevent->type ())
              {
              case QEvent::Close:
                xevent->ignore ();
                emit gh_callback_event (m_handle, "closerequestfcn");
                return true;

              default:
                break;
              }
          }
      }

    return false;
  }

  void
  Figure::eventNotifyAfter (QObject *watched, QEvent *xevent)
  {
    if (! m_blockUpdates)
      {
        if (watched == m_container)
          {
            gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

            switch (xevent->type ())
              {
              case QEvent::Resize:
                updateBoundingBox (true, UpdateBoundingBoxSize);
                break;

              case QEvent::ChildAdded:
                if (dynamic_cast<QChildEvent *> (xevent)->child
                    ()->isWidgetType())
                  {
                    octave::autolock guard (gh_mgr.graphics_lock ());
                    update (figure::properties::ID_TOOLBAR);

                    enableMouseTracking ();
                  }
                break;

              case QEvent::ChildRemoved:
                if (dynamic_cast<QChildEvent *> (xevent)->child
                    ()->isWidgetType())
                  {
                    octave::autolock guard (gh_mgr.graphics_lock ());
                    update (figure::properties::ID_TOOLBAR);
                  }
                break;

              default:
                break;
              }
          }
        else if (watched == m_menuBar)
          {
            switch (xevent->type ())
              {
              case QEvent::ActionAdded:
              case QEvent::ActionChanged:
              case QEvent::ActionRemoved:
                // The menubar may have been resized if no action is visible
                {
                  QAction *a = dynamic_cast<QActionEvent *> (xevent)->action ();
                  int currentHeight = m_menuBar->sizeHint ().height ();
                  if (currentHeight != m_previousHeight
                      && ! a->isSeparator ())
                    updateFigureHeight (m_previousHeight - currentHeight);
                }
                break;

              default:
                break;
              }
          }
        else
          {
            switch (xevent->type ())
              {
              case QEvent::Move:
                updateBoundingBox (false, UpdateBoundingBoxPosition);
                updateBoundingBox (true, UpdateBoundingBoxPosition);
                break;

              case QEvent::Resize:
                updateBoundingBox (false, UpdateBoundingBoxSize);
                break;

              default:
                break;
              }
          }
      }
  }

  void
  Figure::addCustomToolBar (QToolBar *bar, bool visible, bool isdefault)
  {
    QMainWindow *win = qWidget<QMainWindow> ();

    if (isdefault)
      m_figureToolBar = bar;

    if (! visible)
      win->addToolBar (bar);
    else
      {
        QSize sz = bar->sizeHint ();
        QRect r = win->geometry ();

        r.adjust (0, -sz.height (), 0, 0);

        m_blockUpdates = true;
        set_geometry (r);
        win->addToolBarBreak ();
        win->addToolBar (bar);
        m_blockUpdates = false;

        updateBoundingBox (false);
      }
  }

  void
  Figure::showCustomToolBar (QToolBar *bar, bool visible)
  {
    QMainWindow *win = qWidget<QMainWindow> ();

    if ((! bar->isHidden ()) != visible)
      {
        QSize sz = bar->sizeHint ();
        QRect r = win->geometry ();

        if (visible)
          r.adjust (0, -sz.height (), 0, 0);
        else
          r.adjust (0, sz.height (), 0, 0);

        m_blockUpdates = true;
        set_geometry (r);
        bar->setVisible (visible);
        m_blockUpdates = false;

        updateBoundingBox (false);
      }
  }

  void
  Figure::updateContainer (void)
  {
    redraw ();
  }

  void
  Figure::figureWindowShown ()
  {
#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
    QWindow* window = qWidget<QMainWindow> ()->windowHandle ();
    QScreen* screen = window->screen ();

    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    figure::properties& fp = properties<figure> ();
    fp.set___device_pixel_ratio__ (screen->devicePixelRatio ());

    connect (window, &QWindow::screenChanged, this, &Figure::screenChanged);
#endif
  }

  void
  Figure::screenChanged (QScreen* screen)
  {
#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    figure::properties& fp = properties<figure> ();
    double old_dpr = fp.get___device_pixel_ratio__ ();
    double new_dpr = screen->devicePixelRatio ();
    if (old_dpr != new_dpr)
      {
        fp.set___device_pixel_ratio__ (new_dpr);

        // For some obscure reason, changing the __device_pixel_ratio__ property
        // from the GUI thread does not necessarily trigger a redraw.  Force it.
        redraw ();
      }
#else
    octave_unused_parameter (screen);
#endif
  }

  void
  Figure::enableMouseTracking (void)
  {
    // Enable mouse tracking on every widgets
    m_container->setMouseTracking (true);
    m_container->canvas (m_handle)->qWidget ()->setMouseTracking (true);
    for (auto *w : m_container->findChildren<QWidget*> ())
      w->setMouseTracking (true);
  }

}