Added using of Qt::AA_UseHighDpiPixmaps attribute and device pixel ratio (usually set by QT_SCALE_FACTOR env var).
Fixed OCC and Vtk viewers display and selection issues with Qt HDPI scaling.
Fixed OCC and Vtk viewers selection and context menu offset.
#include "OCCViewer_ViewSketcher.h"
#include "OCCViewer_ViewWindow.h"
#include "OCCViewer_ViewPort3d.h"
+#include "ViewerTools_ScreenScaling.h"
#include "QtxRubberBand.h"
{
OCCViewer_ViewPort3d* avp = mypViewWindow->getViewPort();
+ // We need to downscale only non-system events here
+ if (!e->spontaneous())
+ {
+ // Make a copy event with updated coordinates
+ e = ViewerTools_ScreenScaling::getDpiAwareEvent(e, false);
+ }
+
QMouseEvent* me = (QMouseEvent*)e;
SketchState state = EnTrain;
bool ignore = false;
#include "OCCViewer_EnvTextureDlg.h"
#include "OCCViewer_LightSourceDlg.h"
#include "OCCViewer_Utilities.h"
+#include "ViewerTools_ScreenScaling.h"
#include <SUIT_Desktop.h>
#include <SUIT_Session.h>
bool OCCViewer_ViewWindow::eventFilter( QObject* watched, QEvent* e )
{
if ( watched == myViewPort ) {
+ // Makes a copy event with updated coordinates if we need so
+ e = ViewerTools_ScreenScaling::getDpiAwareEvent(e);
+
int aType = e->type();
switch(aType) {
case QEvent::MouseButtonPress:
emit mouseReleased(this, theEvent);
if(theEvent->button() == Qt::RightButton && prevState == -1)
{
+ // We need to pass unscaled coordinates to get a menu painted in a right place.
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
QContextMenuEvent aEvent( QContextMenuEvent::Mouse,
- theEvent->pos(), theEvent->globalPos() );
+ theEvent->pos() / pixelRatio, theEvent->globalPos() / pixelRatio );
emit contextMenuRequested( &aEvent );
}
}
{
Handle(AIS_InteractiveContext) ic = myModel->getAISContext();
bool append = mypSketcher->isHasShift();
+
+ // Sketcher uses unscaled coordinates to draw a rubber band,
+ // then we need to scale them here for proper selection.
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+
switch( mypSketcher->type() )
{
case Rect:
QRect* aRect = (QRect*)mypSketcher->data();
if ( aRect )
{
- int aLeft = aRect->left();
- int aRight = aRect->right();
- int aTop = aRect->top();
- int aBottom = aRect->bottom();
+ int aLeft = aRect->left() * pixelRatio;
+ int aRight = aRect->right() * pixelRatio;
+ int aTop = aRect->top() * pixelRatio;
+ int aBottom = aRect->bottom() * pixelRatio;
// myRect = aRect;
if( append )
for (int index = 1; it != itEnd; ++it, index++)
{
QPoint aPoint = *it;
- anArray.SetValue(index, gp_Pnt2d(aPoint.x(), aPoint.y()));
+ anArray.SetValue(index, gp_Pnt2d(aPoint.x() * pixelRatio, aPoint.y() * pixelRatio));
}
if (append)
#include "SUIT_Tools.h"
#include "SALOME_Actor.h"
+#include "ViewerTools_ScreenScaling.h"
#include <vtkObjectFactory.h>
#include <vtkMath.h>
{
myShiftState = shift;
if( myPoligonState == InProcess ) { // add a new point of polygon
- myPolygonPoints.append( QPoint( x, y ) );
+ // The mouse events were already scaled up with a pixel ratio for a proper selection,
+ // but rubber band's implemented with QPainter scales them on its own.
+ // So, we need to pass unscaled coordinates to get a polygon painted in a right place.
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+
+ myPolygonPoints.append(QPoint(x / pixelRatio, y / pixelRatio));
this->Interactor->GetEventPosition( mySelectionEvent->myX, mySelectionEvent->myY );
mySelectionEvent->myPolygonPoints.append( QPoint( mySelectionEvent->myX, mySelectionEvent->myY ) );
return;
myRectBand = new QtxRectRubberBand( GetRenderWidget() );
myRectBand->setUpdatesEnabled ( false );
- QRect aRect = SUIT_Tools::makeRect(myPoint.x(), myPoint.y(), myOtherPoint.x(), myOtherPoint.y());
+
+ // The mouse events were already scaled up with a pixel ratio for a proper selection,
+ // but rubber band's implemented with QPainter scales them on its own.
+ // So, we need to pass unscaled coordinates to get a rectangle painted in a right place.
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+ QRect aRect = SUIT_Tools::makeRect(
+ myPoint.x() / pixelRatio,
+ myPoint.y() / pixelRatio,
+ myOtherPoint.x() / pixelRatio,
+ myOtherPoint.y() / pixelRatio);
+
myRectBand->initGeometry( aRect );
if ( !myRectBand->isVisible() )
*/
void SVTK_InteractorStyle::drawPolygon()
{
+ // The mouse events were already scaled up with a pixel ratio for a proper selection,
+ // but rubber band's implemented with QPainter scales them on its own.
+ // So, we need to pass unscaled coordinates to get a polygon painted in a right place.
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+ const QPoint myPointCopy(myPoint.x() / pixelRatio, myPoint.y() / pixelRatio);
+ const QPoint myOtherPointCopy(myOtherPoint.x() / pixelRatio, myOtherPoint.y() / pixelRatio);
+
QSize aToler( 5, 5 );
if ( !myPolygonBand ) {
myPolygonBand = new QtxPolyRubberBand( GetRenderWidget() );
QPalette palette;
palette.setColor( myPolygonBand->foregroundRole(), Qt::white );
myPolygonBand->setPalette( palette );
- myPolygonPoints.append( QPoint( myPoint.x(), myPoint.y() ) );
+ myPolygonPoints.append(myPointCopy);
}
myPolygonBand->hide();
bool closed = false;
- bool valid = GetRenderWidget()->rect().contains( QPoint( myOtherPoint.x(), myOtherPoint.y() ) );
+ bool valid = GetRenderWidget()->rect().contains(myOtherPointCopy);
if ( !myPolygonPoints.at(0).isNull() )
{
QRect aRect( myPolygonPoints.at(0).x() - aToler.width(), myPolygonPoints.at(0).y() - aToler.height(),
2 * aToler.width(), 2 * aToler.height() );
- closed = aRect.contains( QPoint( myOtherPoint.x(), myOtherPoint.y() ) );
+ closed = aRect.contains(myOtherPointCopy);
}
QPolygon* points = new QPolygon( myPolygonPoints );
- valid = valid && isValid( points, QPoint( myOtherPoint.x(), myOtherPoint.y() ) );
+ valid = valid && isValid(points, myOtherPointCopy);
myPoligonState = valid ? InProcess : NotValid;
delete points;
if ( closed && !valid )
else
GetRenderWidget()->setCursor( Qt::ForbiddenCursor );
- myPolygonPoints.append( QPoint( myOtherPoint.x(), myOtherPoint.y() ) );
+ myPolygonPoints.append(myOtherPointCopy);
QPolygon aPolygon( myPolygonPoints );
myPolygonBand->initGeometry( aPolygon );
#include "SVTK_Renderer.h"
#include "SVTK_Functor.h"
#include "SALOME_Actor.h"
+#include "ViewerTools_ScreenScaling.h"
// QT Includes
// Put Qt includes before the X11 includes which #define the symbol None
::polish()
{
// Final initialization just before the widget is displayed
- GetDevice()->SetSize(width(),height());
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+ GetDevice()->SetSize(width() * pixelRatio, height() * pixelRatio);
if(!GetDevice()->GetInitialized() && GetDevice()->GetRenderWindow()){
GetDevice()->Initialize();
GetDevice()->ConfigureEvent();
*/
void
QVTK_RenderWindowInteractor
-::resizeEvent( QResizeEvent* /*theEvent*/ )
+::resizeEvent( QResizeEvent* /* theEvent */ )
{
+
int* aSize = getRenderWindow()->GetSize();
int aWidth = aSize[0];
int aHeight = aSize[1];
- GetDevice()->UpdateSize(width(),height());
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+ GetDevice()->UpdateSize(width() * pixelRatio, height() * pixelRatio);
if(isVisible() && aWidth && aHeight){
if( aWidth != width() || aHeight != height() ) {
SVTK_RenderWindowInteractor
::mouseMoveEvent( QMouseEvent* event )
{
+ event = static_cast<QMouseEvent*>(ViewerTools_ScreenScaling::getDpiAwareEvent(event));
QVTK_RenderWindowInteractor::mouseMoveEvent(event);
if(GENERATE_SUIT_EVENTS)
SVTK_RenderWindowInteractor
::mousePressEvent( QMouseEvent* event )
{
+ event = static_cast<QMouseEvent*>(ViewerTools_ScreenScaling::getDpiAwareEvent(event));
QVTK_RenderWindowInteractor::mousePressEvent(event);
if(GENERATE_SUIT_EVENTS)
isOperation = style->CurrentState() != VTK_INTERACTOR_STYLE_CAMERA_NONE;
}
+ event = static_cast<QMouseEvent*>(ViewerTools_ScreenScaling::getDpiAwareEvent(event));
QVTK_RenderWindowInteractor::mouseReleaseEvent(event);
if ( style ) {
if ( aRightBtn && !isOperation && !isPolygonalSelection &&
!( event->modifiers() & Qt::ControlModifier ) &&
!( event->modifiers() & Qt::ShiftModifier ) ) {
+ // We need to pass unscaled coordinates to get a menu painted in a right place.
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
QContextMenuEvent aEvent( QContextMenuEvent::Mouse,
- event->pos(), event->globalPos() );
+ event->pos() / pixelRatio, event->globalPos() / pixelRatio);
emit contextMenuRequested( &aEvent );
}
if(GENERATE_SUIT_EVENTS)
SVTK_RenderWindowInteractor
::mouseDoubleClickEvent( QMouseEvent* event )
{
+ event = static_cast<QMouseEvent*>(ViewerTools_ScreenScaling::getDpiAwareEvent(event));
+
if( GetInteractorStyle() && event->button() == Qt::LeftButton ) {
SVTK_InteractorStyle* style = dynamic_cast<SVTK_InteractorStyle*>( GetInteractorStyle() );
if ( style )
SVTK_RenderWindowInteractor
::wheelEvent( QWheelEvent* event )
{
+ event = static_cast<QWheelEvent*>(ViewerTools_ScreenScaling::getDpiAwareEvent(event));
+
QVTK_RenderWindowInteractor::wheelEvent(event);
if(event->delta() > 0)
QApplication::setApplicationName("salome");
QApplication::setApplicationVersion(salomeVersion());
+ // supports HDPI
+ MESSAGE("Set QApplication attributes to supports HDPI...");
+
+ // Make QIcon::pixmap() generate high-dpi pixmaps that can be larger than the requested size.
+ // Such pixmaps will have devicePixelRatio() set to a value higher than 1.
+ // After setting this attribute, application code that uses pixmap sizes in layout geometry calculations
+ // should typically divide by devicePixelRatio() to get device-independent layout geometry.
+ QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+
+ // The lines below do the same - enables automatic scaling, based on the monitor's pixel density.
+ // This won't change the size of point-sized fonts, since point is a physical measurement unit.
+ // Using them can make an impression of large icons among small fonts.
+ // qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
+ // QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // Doesn't scale fonts
+
+ // Keep this line as an example of using explicit scale factor.
+ // Defines a global scale factor for the whole application, including point-sized fonts.
+ // qputenv("QT_SCALE_FACTOR", "1.5"); // Scales everything
+
// Install Qt debug messages handler
MsgHandler msgHandler;
qInstallMessageHandler(QtxMsgHandler);
ADD_DEFINITIONS(${QT_DEFINITIONS})
# libraries to link to
-SET(_link_LIBRARIES ${QT_LIBRARIES} qtx)
+SET(_link_LIBRARIES
+ ${QT_LIBRARIES}
+ ${KERNEL_SALOMELocalTrace}
+ qtx)
# --- headers ---
ViewerTools_CubeAxesDlgBase.h
ViewerTools_DialogBase.h
ViewerTools_FontWidgetBase.h
+ ViewerTools_ScreenScaling.h
)
# header files / no moc processing
ViewerTools_CubeAxesDlgBase.cxx
ViewerTools_DialogBase.cxx
ViewerTools_FontWidgetBase.cxx
+ ViewerTools_ScreenScaling.cxx
)
# sources / to compile
--- /dev/null
+// Copyright (C) 2007-2024 CEA, EDF, OPEN CASCADE
+//
+// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "ViewerTools_ScreenScaling.h"
+
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QEvent>
+#include <QMouseEvent>
+#include <QWheelEvent>
+
+#include "utilities.h"
+
+/*!
+ * Namespace : ViewerTools_ScreenScaling
+ * Description : Tools for handle UI on screens with different scaling
+ */
+namespace ViewerTools_ScreenScaling
+{
+ // Returns pixel ratio for the current screen
+ double getPR()
+ {
+ auto getPixelRatio = []() -> double
+ {
+ // Returns the device pixel ratio for the device as a floating point number.
+ // Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" displays.
+ // Returns QT_SCALE_FACTOR if QT_SCALE_FACTOR was set.
+ // Returns 2 on HDPI if QApplication::setAttribute(Qt::AA_EnableHighDpiScaling) was set.
+ // Returns 2 * QT_SCALE_FACTOR if both of the features above were set.
+ // NOTE. QT_SCALE_FACTOR breaks UI in a set (-1.0, 0.0) and (0.0, 1.0).
+ // Negative values don't scale or break UI down, but end up with 1.0 pixel ratio.
+ const double pixelRatio = QApplication::desktop()->devicePixelRatioF();
+ MESSAGE("pixelRatio: " << pixelRatio);
+
+ // Keep commented block here for a test case where we use QT_SCALE_FACTOR value instead.
+ // QByteArray scaleFactorArr = qgetenv("QT_SCALE_FACTOR");
+ // const QString scaleFactorStr = QString::fromLocal8Bit(scaleFactorArr);
+
+ // bool isScale = false;
+ // const double scaleFactor = scaleFactorStr.toDouble(&isScale);
+ // if (isScale)
+ // {
+ // MESSAGE("scaleFactor: " << scaleFactor);
+ // return scaleFactor;
+ // }
+
+ return pixelRatio;
+ };
+
+ // TODO: check if we need to get it out of static to handle moving to another screen
+ static const double pixelRatio = getPixelRatio();
+ return pixelRatio;
+ }
+
+ // Check if we have pixel ratio != 1.0
+ bool isScaledPixelRatio()
+ {
+ auto isScaledPR = []() -> bool
+ {
+ // This an arbitrary value that seems to be meaningful for UI scaling.
+ // It's not clear if we need smaller values.
+ const double epsilon = 0.01;
+ const double pixelRatio = ViewerTools_ScreenScaling::getPR();
+
+ const bool isScaled = std::abs(pixelRatio - 1.0) > epsilon;
+ MESSAGE("isScaled: " << isScaled);
+
+ return isScaled;
+ };
+
+ static const bool isScaled = isScaledPR();
+ return isScaled;
+ }
+
+
+ // Returns a copy of the given event with the local coordinates
+ // updated with the current pixel ratio.
+ QEvent* getDpiAwareEvent(QEvent* e, bool toMultiply/* = true */)
+ {
+ // Calculate a new position
+ auto getNewPos = [toMultiply](const QPointF& pos) -> QPointF
+ {
+ double pixelRatio = ViewerTools_ScreenScaling::getPR();
+ if (!toMultiply)
+ {
+ pixelRatio = 1.0 / pixelRatio;
+ }
+
+ const double x = pos.x() * pixelRatio;
+ const double y = pos.y() * pixelRatio;
+
+ // Commented because of bloated output for casual debug
+ // MESSAGE("New pos: " << x << ", " << y);
+
+ return QPointF(x, y);
+ };
+
+ // Scales mouse event
+ auto makeMouseEvent = [&getNewPos](QMouseEvent* e) -> QEvent*
+ {
+ const QPointF pos = e->localPos();
+ const QPointF newPos = getNewPos(pos);
+
+ const QPointF globalPos = e->globalPos();
+ const QPointF globalNewPos = getNewPos(globalPos);
+
+ // Commented because of bloated output for casual debug
+ // MESSAGE("type: " << e->type() << "; old: " << pos.x() << ", " << pos.y() << "; new: " << newPos.x() << ", " << newPos.y());
+
+ return new QMouseEvent(e->type(), newPos, globalNewPos, e->button(), e->buttons(), e->modifiers());
+ };
+
+ // Scales wheel event
+ auto makeWheelEvent = [&getNewPos](QWheelEvent* e) -> QEvent*
+ {
+ const QPointF pos = e->posF();
+
+ // Commented because of bloated output for casual debug
+ // MESSAGE("Old pos: " << pos.x() << ", " << pos.y());
+
+ return new QWheelEvent(
+ getNewPos(pos), e->globalPosF(), e->pixelDelta(), e->angleDelta(), e->buttons(), e->modifiers(), e->phase(), e->inverted(), e->source());
+ };
+
+ // Return on start if we don't have any scaling
+ if (!isScaledPixelRatio())
+ return e;
+
+ // Make a copy of the event with the new position
+ const int aType = e->type();
+ switch(aType)
+ {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonDblClick:
+ return makeMouseEvent((QMouseEvent*)e);
+
+ case QEvent::Wheel:
+ return makeWheelEvent((QWheelEvent*)e);
+
+ default:
+ break;
+ }
+
+ return e;
+ }
+};
--- /dev/null
+// Copyright (C) 2007-2024 CEA, EDF, OPEN CASCADE
+//
+// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef VIEWERTOOLS_SCREENSCALING_H
+#define VIEWERTOOLS_SCREENSCALING_H
+
+#include "ViewerTools.h"
+
+class QEvent;
+
+/*!
+ * Namespace : ViewerTools_ScreenScaling
+ * Description : Tools for handle UI on screens with different scaling
+ */
+namespace ViewerTools_ScreenScaling
+{
+ // Returns pixel ratio for the current screen
+ VIEWERTOOLS_EXPORT double getPR();
+
+ // Check if we have pixel ratio != 1.0
+ VIEWERTOOLS_EXPORT bool isScaledPixelRatio();
+
+ // Returns a copy of the given event with the local coordinates
+ // updated with the current pixel ratio.
+ VIEWERTOOLS_EXPORT QEvent* getDpiAwareEvent(QEvent* e, bool toMultiply = true);
+}
+
+#endif