# HG changeset patch
# User John Swensen <jpswensen@gmail.com>
# Date 1536997428 -7200
#      Sat Sep 15 09:43:48 2018 +0200
# Node ID 9e73b7fe701f480e30e88510290a722f0a8904e2
# Parent  369e43cda3c7e616c02441479e1df8e9b5c532f3
Improve OpenGL rendering on high resolution screens with Qt 5 (bug #49053)

* FigureWindow.cc/h (FigureWindow::figureWindowShown): New signal
  (FigureWindow::showEvent): Overload QMainWindow::showEvent to also emit
  figureWindowShown signal.

* Figure.cc/h (Figure::figureWindowShown): New slot. Set the figure object
  __device_pixel_ratio__ property and connect the window's screenChanged signal
  to Figure::screenChanged slot.
  (Figure::screenChanged): New slot. Update the figure object's
  __device_pixel_ratio__ property.

* GLCanvas.cc (GLCanvas::draw, GLCanvas::selectFromAxes) scale the
  opengl_rederer's viewport with the correct __device_pixel_ratio__

* gl-render.cc/h (opengl_renderer::m_devpixratio): New data member to store
  the current screen's __device_pixel_ratio__
  (opengl_renderer::draw_figure): Set m_devpixratio.
  (opengl_renderer::setup_opengl_transformation,::draw_text_background,
  ::opengl_renderer::init_marker): Scale width and height of the figure
  when setting up glOrtho transform.
  (opengl_renderer::draw_image): Scale zoom factors when using glPixelZoom
  (opengl_renderer::set_font): Scale font size.
  (opengl_renderer::set_linewidth): Scale line width.

* graphics.cc (device_pixel_ratio): New static function to get the ancestor
  figure's __devive_pixel_ratio__.
  (axes::properties::update_font, text::properties::update_font): Scale font
  size.
  (axes::properties::get_ticklabel_extents): Scale tick labels size.
  (text::properties::get_extent): Scale text size.
  (figure::properties::update_text_pos): New mehtod to update the position of
  axes objects annotations and text objects.

* graphics.in.h (figure::properties::__device_pixel_ratio__,
  figure::properties::update__device_pixel_ratio__): New property and updater.
  (axes::properties::update_font, text::properties::update_font,
  text::properties::update_text_extent): Make methods public so that
  update_text_pos can access them.

* acinclude.m4: Determine if QScreen::devicePixelRatio is present in the Qt
  library in use.

* print.m: set __device_pixel_ratio__ to 1 when printing.

diff -r 369e43cda3c7 -r 9e73b7fe701f libgui/graphics/Figure.cc
--- a/libgui/graphics/Figure.cc	Thu Sep 13 11:05:43 2018 -0700
+++ b/libgui/graphics/Figure.cc	Sat Sep 15 09:43:48 2018 +0200
@@ -41,6 +41,8 @@
 #include <QtDebug>
 #include <QTimer>
 #include <QToolBar>
+#include <QWindow>
+#include <QScreen>
 
 #include "Canvas.h"
 #include "Container.h"
@@ -114,6 +116,11 @@
 
     figure::properties& fp = properties<figure> ();
 
+    // Register for the signal that indicates when a window has moved
+    // to a different screen
+    connect (win, SIGNAL (figureWindowShown ()),
+             this, SLOT (figureWindowShown ()));
+
     // Status bar
     m_statusBar = win->statusBar ();
     int boffset = 0;
@@ -921,6 +928,34 @@
   }
 
   void
+  Figure::figureWindowShown ()
+  {
+#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
+    QWindow* window = qWidget<QMainWindow> ()->windowHandle ();
+    QScreen* screen = window->screen ();
+    
+    gh_manager::auto_lock lock;
+    
+    figure::properties& fp = properties<figure> ();
+    fp.set___device_pixel_ratio__ (screen->devicePixelRatio ());
+
+    connect (window, SIGNAL (screenChanged (QScreen*)),
+             this, SLOT (screenChanged (QScreen*)));
+#endif
+  }
+
+  void
+  Figure::screenChanged (QScreen* screen)
+  {
+#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
+    gh_manager::auto_lock lock;
+    
+    figure::properties& fp = properties<figure> ();
+    fp.set___device_pixel_ratio__ (screen->devicePixelRatio ());
+#endif
+  }
+
+  void
   Figure::enableMouseTracking (void)
   {
     // Enable mouse tracking on every widgets
diff -r 369e43cda3c7 -r 9e73b7fe701f libgui/graphics/Figure.h
--- a/libgui/graphics/Figure.h	Thu Sep 13 11:05:43 2018 -0700
+++ b/libgui/graphics/Figure.h	Sat Sep 15 09:43:48 2018 +0200
@@ -32,6 +32,7 @@
 
 class QMainWindow;
 class QToolBar;
+class QScreen;
 
 namespace QtHandles
 {
@@ -122,6 +123,8 @@
     void toggleAxes (void);
     void toggleGrid (void);
     void autoAxes (void);
+    void figureWindowShown ();
+    void screenChanged (QScreen*);
 
   public slots:
     uint8NDArray slotGetPixels (void);
diff -r 369e43cda3c7 -r 9e73b7fe701f libgui/graphics/FigureWindow.cc
--- a/libgui/graphics/FigureWindow.cc	Thu Sep 13 11:05:43 2018 -0700
+++ b/libgui/graphics/FigureWindow.cc	Sat Sep 15 09:43:48 2018 +0200
@@ -48,4 +48,10 @@
     return nullptr;
   }
 
+  void FigureWindow::showEvent (QShowEvent* ev)
+  {
+    QMainWindow::showEvent (ev);
+    emit figureWindowShown();
+  }
+
 }
diff -r 369e43cda3c7 -r 9e73b7fe701f libgui/graphics/FigureWindow.h
--- a/libgui/graphics/FigureWindow.h	Thu Sep 13 11:05:43 2018 -0700
+++ b/libgui/graphics/FigureWindow.h	Sat Sep 15 09:43:48 2018 +0200
@@ -37,10 +37,17 @@
     Q_OBJECT
 
   public:
-    FigureWindow (QWidget *parent = nullptr);
+    explicit FigureWindow (QWidget *parent = nullptr);
     ~FigureWindow (void);
 
     QMenu * createPopupMenu (void);
+
+  protected:
+    void showEvent(QShowEvent *ev);
+
+  signals:
+    void figureWindowShown();
+
   };
 
 }
diff -r 369e43cda3c7 -r 9e73b7fe701f libgui/graphics/GLCanvas.cc
--- a/libgui/graphics/GLCanvas.cc	Thu Sep 13 11:05:43 2018 -0700
+++ b/libgui/graphics/GLCanvas.cc	Sat Sep 15 09:43:48 2018 +0200
@@ -72,7 +72,9 @@
 
     if (go)
       {
-        m_renderer.set_viewport (width (), height ());
+        graphics_object fig = go.get_ancestor ("figure");
+        double dpr = fig.get ("__device_pixel_ratio__").double_value ();
+        m_renderer.set_viewport (dpr * width (), dpr * height ());
         m_renderer.draw (go);
       }
   }
@@ -175,7 +177,10 @@
       {
         octave::opengl_selector s (m_glfcns);
 
-        s.set_viewport (width (), height ());
+        graphics_object fig = ax.get_ancestor ("figure");
+        double dpr = fig.get ("__device_pixel_ratio__").double_value ();
+
+        s.set_viewport (dpr * width (), dpr * height ());
         return s.select (ax, pt.x (), height () - pt.y (),
                          octave::select_ignore_hittest);
       }
diff -r 369e43cda3c7 -r 9e73b7fe701f libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc	Thu Sep 13 11:05:43 2018 -0700
+++ b/libinterp/corefcn/gl-render.cc	Sat Sep 15 09:43:48 2018 +0200
@@ -617,7 +617,8 @@
       ymin (), ymax (), zmin (), zmax (), xZ1 (), xZ2 (),
       marker_id (), filled_marker_id (), camera_pos (), camera_dir (),
       view_vector (), interpreter ("none"), txt_renderer (),
-      m_current_light (0), m_max_lights (0), selecting (false)
+      m_current_light (0), m_max_lights (0), selecting (false),
+      m_devpixratio (1.)
   {
     // This constructor will fail if we don't have OpenGL or if the data
     // types we assumed in our public interface aren't compatible with the
@@ -702,8 +703,11 @@
   void
   opengl_renderer::draw_figure (const figure::properties& props)
   {
+    // Current physical-pixel to toolkit-pixel factor
+    m_devpixratio = props.get___device_pixel_ratio__ ();
+    
     // Initialize OpenGL context
-
+    
     init_gl_context (props.is_graphicssmoothing (), props.get_color_rgb ());
 
 #if defined (HAVE_OPENGL)
@@ -1222,6 +1226,11 @@
     m_glfcns.glMultMatrixd (x_mat1.data ());
     m_glfcns.glMatrixMode (GL_PROJECTION);
     m_glfcns.glLoadIdentity ();
+
+     // Use abstract Octave-pixels for transformation, not physical-pixels
+     vw[2] = octave::math::round (static_cast<float> (vw[2]) / m_devpixratio);
+     vw[3] = octave::math::round (static_cast<float> (vw[3]) / m_devpixratio);
+
     m_glfcns.glOrtho (0, vw[2], vw[3], 0, xZ1, xZ2);
     m_glfcns.glMultMatrixd (x_mat2.data ());
     m_glfcns.glMatrixMode (GL_MODELVIEW);
@@ -3686,6 +3695,10 @@
     m_glfcns.glMatrixMode (GL_PROJECTION);
     m_glfcns.glPushMatrix ();
     m_glfcns.glLoadIdentity ();
+
+    vp[2] = octave::math::round (static_cast<float> (vp[2]) / m_devpixratio);
+    vp[3] = octave::math::round (static_cast<float> (vp[3]) / m_devpixratio);
+
     m_glfcns.glOrtho (0, vp[2], vp[3], 0, xZ1, xZ2);
     m_glfcns.glMatrixMode (GL_MODELVIEW);
     m_glfcns.glPushMatrix ();
@@ -3858,7 +3871,8 @@
     if (i0 >= i1 || j0 >= j1)
       return;
 
-    m_glfcns.glPixelZoom (pix_dx, -pix_dy);
+    m_glfcns.glPixelZoom (m_devpixratio * pix_dx,
+                          - m_devpixratio * pix_dy);
     m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0);
 
     // by default this is 4
@@ -4069,11 +4083,12 @@
 
   void
   opengl_renderer::set_font (const base_properties& props)
-  {
+  {    
     txt_renderer.set_font (props.get ("fontname").string_value (),
                            props.get ("fontweight").string_value (),
                            props.get ("fontangle").string_value (),
-                           props.get ("__fontsize_points__").double_value ());
+                           props.get ("__fontsize_points__").double_value ()
+                           * m_devpixratio);
   }
 
   void
@@ -4111,7 +4126,7 @@
   {
 #if defined (HAVE_OPENGL)
 
-    m_glfcns.glLineWidth (w);
+    m_glfcns.glLineWidth (w * m_devpixratio);
 
 #else
 
@@ -4261,6 +4276,10 @@
     m_glfcns.glMatrixMode (GL_PROJECTION);
     m_glfcns.glPushMatrix ();
     m_glfcns.glLoadIdentity ();
+
+    vw[2] = octave::math::round (static_cast<float> (vw[2]) / m_devpixratio);
+    vw[3] = octave::math::round (static_cast<float> (vw[3]) / m_devpixratio);
+
     m_glfcns.glOrtho (0, vw[2], vw[3], 0, xZ1, xZ2);
     m_glfcns.glMatrixMode (GL_MODELVIEW);
     m_glfcns.glPushMatrix ();
diff -r 369e43cda3c7 -r 9e73b7fe701f libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h	Thu Sep 13 11:05:43 2018 -0700
+++ b/libinterp/corefcn/gl-render.h	Sat Sep 15 09:43:48 2018 +0200
@@ -244,6 +244,9 @@
 
     // Indicate we are drawing for selection purpose
     bool selecting;
+
+    // Factor used for translating Octave pixels to actual device pixels
+    double m_devpixratio;
   private:
     class patch_tesselator;
   };
diff -r 369e43cda3c7 -r 9e73b7fe701f libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Thu Sep 13 11:05:43 2018 -0700
+++ b/libinterp/corefcn/graphics.cc	Sat Sep 15 09:43:48 2018 +0200
@@ -870,6 +870,19 @@
                            sz.extract_n (0, 2, 1, 2)).extract_n (0, 2, 1, 2);
 }
 
+static double
+device_pixel_ratio (graphics_handle h)
+{
+  double retval = 1.0;
+  
+  graphics_object fig = gh_manager::get_object (h).get_ancestor ("figure");
+  
+  if (fig.valid_object ())
+    retval = fig.get ("__device_pixel_ratio__").double_value ();
+  
+  return retval;
+}
+
 static void
 convert_cdata_2 (bool is_scaled, bool is_real, double clim_0, double clim_1,
                  const double *cmapv, double x, octave_idx_type lda,
@@ -2075,6 +2088,45 @@
 
   base_properties::update_handlevisibility ();
 }
+
+static void
+update_text_pos (graphics_handle h)
+{
+  graphics_object go = gh_manager::get_object (h);
+  if (go.isa ("text"))
+    {
+      text::properties& tp
+        = dynamic_cast<text::properties&> (go.get_properties ());
+      tp.update_font ();
+      tp.update_text_extent ();
+    }
+  else if (go.isa ("figure") || go.isa ("uipanel") || go.isa ("axes")
+           || go.isa ("hggroup"))
+    {
+      Matrix ch = go.get_properties ().get_all_children ();
+      for (octave_idx_type ii = 0; ii < ch.numel (); ii++)
+        update_text_pos (graphics_handle (ch(ii)));
+
+      if (go.isa ("axes"))
+        {
+          axes::properties& ap
+            = dynamic_cast<axes::properties&> (go.get_properties ());
+          ap.update_font ();
+          ap.update_xlabel_position ();
+          ap.update_ylabel_position ();
+          ap.update_zlabel_position ();
+          ap.update_title_position ();
+          ap.update_axes_layout ();
+        }
+    }
+}
+
+void
+figure::properties::update___device_pixel_ratio__ (void)
+{
+  update_text_pos (get___myhandle__ ());
+}
+
 // ---------------------------------------------------------------------
 
 void
@@ -6641,11 +6693,13 @@
 
     }
 
+  double dpr = device_pixel_ratio (get___myhandle__ ());
+
   gh_manager::auto_lock guard;
   txt_renderer.set_font (get ("fontname").string_value (),
                          get ("fontweight").string_value (),
                          get ("fontangle").string_value (),
-                         get ("__fontsize_points__").double_value ());
+                         get ("__fontsize_points__").double_value () * dpr);
 }
 
 // The INTERNAL flag defines whether position or outerposition is used.
@@ -7714,6 +7768,7 @@
 {
   Matrix ext (1, 2, 0.0);
   double wmax, hmax;
+  double dpr = device_pixel_ratio (get___myhandle__ ());
   wmax = hmax = 0.0;
   int n = std::min (ticklabels.numel (), ticks.numel ());
   for (int i = 0; i < n; i++)
@@ -7731,8 +7786,8 @@
               ext = txt_renderer.get_extent (label, 0.0,
                                              get_ticklabelinterpreter ());
 
-              wmax = std::max (wmax, ext(0));
-              hmax = std::max (hmax, ext(1));
+              wmax = std::max (wmax, ext(0) / dpr);
+              hmax = std::max (hmax, ext(1) / dpr);
             }
           else
             {
@@ -8788,7 +8843,14 @@
   m(0) += p(0);
   m(1) += p(1);
 
-  return convert_text_position (m, *this, "pixels", get_units ());
+  Matrix bbox = convert_text_position (m, *this, "pixels", get_units ());
+
+  double dpr = device_pixel_ratio (get___myhandle__ ());
+
+  for (octave_idx_type ii = 0; ii < bbox.numel (); ii++)
+    bbox(ii) = bbox(ii) / dpr;
+  
+  return bbox;
 }
 
 void
@@ -8826,11 +8888,13 @@
 void
 text::properties::update_font (void)
 {
+  double dpr = device_pixel_ratio (get___myhandle__ ());
+
   gh_manager::auto_lock guard;
   txt_renderer.set_font (get ("fontname").string_value (),
                          get ("fontweight").string_value (),
                          get ("fontangle").string_value (),
-                         get ("__fontsize_points__").double_value ());
+                         get ("__fontsize_points__").double_value () * dpr);
 
   txt_renderer.set_color (get_color_rgb ());
 }
@@ -8870,7 +8934,7 @@
   // The bbox is relative to the text's position.  We'll leave it that
   // way, because get_position does not return valid results when the
   // text is first constructed.  Conversion to proper coordinates is
-  // performed in get_extent.
+  // performed in get_extent.  
   set_extent (bbox);
 
   if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
@@ -8927,9 +8991,9 @@
   double fontsz = get_fontsize ();
   double parent_height = box_pix_height;
 
+  graphics_object go (gh_manager::get_object (get___myhandle__ ()));
   if (fontunits_is ("normalized") && parent_height <= 0)
     {
-      graphics_object go (gh_manager::get_object (get___myhandle__ ()));
       graphics_object ax (go.get_ancestor ("axes"));
 
       parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
diff -r 369e43cda3c7 -r 9e73b7fe701f libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Thu Sep 13 11:05:43 2018 -0700
+++ b/libinterp/corefcn/graphics.in.h	Sat Sep 15 09:43:48 2018 +0200
@@ -3183,6 +3183,7 @@
       any_property __plot_stream__ h , Matrix ()
       any_property __rotate_mode__ h , Matrix ()
       any_property __zoom_mode__ h , Matrix ()
+      double_property __device_pixel_ratio__ hU , 1.0
 
       // Obsolete properties: doublebuffer, mincolormap, wvisual, wvisualmode,
       //                      xdisplay, xvisual, xvisualmode
@@ -3529,6 +3530,8 @@
 
     void update_units (const caseless_str& old_units);
 
+    void update_font (std::string prop = "");
+
     void update_fontunits (const caseless_str& old_fontunits);
 
     void increase_num_lights (void) { num_lights++; }
@@ -3957,7 +3960,6 @@
         calc_ticklabels (ztick, zticklabel, zscale.is ("log"), false, 2, zlim);
     }
 
-    void update_font (std::string prop = "");
     void update_fontname (void)
     {
       update_font ("fontname");
@@ -4388,6 +4390,10 @@
   public:
     double get___fontsize_points__ (double box_pix_height = 0) const;
 
+    void update_text_extent (void);
+
+    void update_font (void);
+
     void set_position (const octave_value& val)
     {
       octave_value new_val (val);
@@ -4497,15 +4503,12 @@
         set_zliminclude ("off");
     }
 
-    void update_text_extent (void);
-
     void request_autopos (void);
     void update_positionmode (void) { request_autopos (); }
     void update_rotationmode (void) { request_autopos (); }
     void update_horizontalalignmentmode (void) { request_autopos (); }
     void update_verticalalignmentmode (void) { request_autopos (); }
 
-    void update_font (void);
     void update_string (void) { request_autopos (); update_text_extent (); }
     void update_rotation (void) { update_text_extent (); }
     void update_color (void) { update_font (); update_text_extent (); }
diff -r 369e43cda3c7 -r 9e73b7fe701f m4/acinclude.m4
--- a/m4/acinclude.m4	Thu Sep 13 11:05:43 2018 -0700
+++ b/m4/acinclude.m4	Sat Sep 15 09:43:48 2018 +0200
@@ -666,6 +666,36 @@
   fi
 ])
 dnl
+dnl Check whether the Qt class QScreen has the devicePixelRatio member function.
+dnl This member function was introduced in Qt 5.5.
+dnl
+AC_DEFUN([OCTAVE_CHECK_FUNC_QSCREEN_DEVICEPIXELRATIO], [
+  AC_CACHE_CHECK([for QScreen::devicePixelRatio in <QScreen>],
+    [octave_cv_func_qscreen_devicepixelratio],
+    [AC_LANG_PUSH(C++)
+    ac_octave_save_CPPFLAGS="$CPPFLAGS"
+    ac_octave_save_CXXFLAGS="$CXXFLAGS"
+    CPPFLAGS="$QT_CPPFLAGS $CXXPICFLAG $CPPFLAGS"
+    CXXFLAGS="$CXXPICFLAG $CXXFLAGS"
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+        #include <QApplication>
+        #include <QScreen>
+        ]], [[
+        QScreen *screen = QApplication::primaryScreen ();
+        qreal ratio = screen->devicePixelRatio ();
+        ]])],
+      octave_cv_func_qscreen_devicepixelratio=yes,
+      octave_cv_func_qscreen_devicepixelratio=no)
+    CPPFLAGS="$ac_octave_save_CPPFLAGS"
+    CXXFLAGS="$ac_octave_save_CXXFLAGS"
+    AC_LANG_POP(C++)
+  ])
+  if test $octave_cv_func_qscreen_devicepixelratio = yes; then
+    AC_DEFINE(HAVE_QSCREEN_DEVICEPIXELRATIO, 1,
+      [Define to 1 if you have the `QScreen::devicePixelRatio' member function.])
+  fi
+])
+dnl
 dnl Check whether the Qt class QTabWidget has the setMovable member function.
 dnl This member function was introduced in Qt 4.5.
 dnl
@@ -2107,6 +2137,7 @@
     OCTAVE_CHECK_FUNC_QLINEEDIT_SETPLACEHOLDERTEXT
     OCTAVE_CHECK_FUNC_QMOUSEEVENT_LOCALPOS
     OCTAVE_CHECK_FUNC_QOBJECT_FINDCHILDREN_ACCEPTS_FINDCHILDOPTIONS
+    OCTAVE_CHECK_FUNC_QSCREEN_DEVICEPIXELRATIO
     OCTAVE_CHECK_FUNC_QTABWIDGET_SETMOVABLE
     OCTAVE_CHECK_FUNC_QTMESSAGEHANDLER_ACCEPTS_QMESSAGELOGCONTEXT
     OCTAVE_CHECK_MEMBER_QFONT_FORCE_INTEGER_METRICS
diff -r 369e43cda3c7 -r 9e73b7fe701f scripts/plot/util/print.m
--- a/scripts/plot/util/print.m	Thu Sep 13 11:05:43 2018 -0700
+++ b/scripts/plot/util/print.m	Sat Sep 15 09:43:48 2018 +0200
@@ -386,6 +386,15 @@
       nfig += 1;
     endfor
 
+    ## Don't account for the actual pixel density
+    if (strcmp (tk, "qt"))
+      props(end+1).h = opts.figure;
+      props(end).name = "__device_pixel_ratio__";
+      props(end).value = {get(opts.figure, "__device_pixel_ratio__")};
+      set (opts.figure, "__device_pixel_ratio__", 1);
+      nfig += 1;
+    endif
+
     ## print() requires axes units = "normalized"
     hax = findall (opts.figure, "-depth", 1, "type", "axes", ...
       "-not", "units", "normalized");
