Move the tooltip out of the way of very large mouse cursors

Users that have large mouse pointers configured in their settings
can not see tooltips, as they are obscured by the pointer.
Native applications on Windows and macOS have the same problem,
which includes the tooltips for the minimize/maximize/close controls
in the window frame of e.g. Explorer.

Introduce QPlatformCursor::size that returns a value that is based
on the user's settings, or a default value. We can then use that
value to move the tooltip out of the way.

On Windows, the calculation of the cursor size is based on
experimenting with the settings, which are in logical independent
pixels. The placement of the tooltip attempts to keep existing
behavior, and to not end up with a tooltip that's very far away
from the tip of the arrow even for very large mouse cursors.

[ChangeLog][QtWidgets][QToolTip] Make sure that the tooltip
is not obscured by very large mouse pointers on Windows and macOS.

Change-Id: I8e13b7a166bfe8b59cef4765c950f90fefeaef9d
Fixes: QTBUG-79627
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
bb10
Volker Hilsheimer 2019-11-05 14:06:46 +01:00
parent 5272c35073
commit baed8534bc
7 changed files with 91 additions and 14 deletions

View File

@ -131,6 +131,14 @@ void QPlatformCursor::setPos(const QPoint &pos)
QWindowSystemInterface::handleMouseEvent(0, pos, pos, Qt::NoButton, Qt::NoButton, QEvent::MouseMove);
}
/*!
Returns the size of the cursor, in native pixels.
*/
QSize QPlatformCursor::size() const
{
return QSize(16, 16);
}
// End of display and pointer event handling code
// Beginning of built-in cursor graphics
// from src/gui/embedded/QGraphicsSystemCursorImage_qws.cpp

View File

@ -96,6 +96,7 @@ public:
#endif // QT_NO_CURSOR
virtual QPoint pos() const;
virtual void setPos(const QPoint &pos);
virtual QSize size() const;
static Capabilities capabilities() { return m_capabilities; }
static void setCapabilities(Capabilities c) { m_capabilities = c; }

View File

@ -56,6 +56,9 @@ public:
void changeCursor(QCursor *cursor, QWindow *window) override;
QPoint pos() const override;
void setPos(const QPoint &position) override;
QSize size() const override;
private:
QHash<Qt::CursorShape, NSCursor *> m_cursors;
NSCursor *convertCursor(QCursor *cursor);

View File

@ -85,6 +85,31 @@ void QCocoaCursor::setPos(const QPoint &position)
CFRelease(e);
}
QSize QCocoaCursor::size() const
{
NSCursor *cocoaCursor = NSCursor.currentSystemCursor;
if (!cocoaCursor)
return QPlatformCursor::size();
NSImage *cursorImage = cocoaCursor.image;
if (!cursorImage)
return QPlatformCursor::size();
QSizeF size = QSizeF::fromCGSize(cursorImage.size);
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
NSDictionary *accessSettings = [defaults persistentDomainForName:@"com.apple.universalaccess"];
if (accessSettings == nil)
return size.toSize();
float sizeScale = [accessSettings[@"mouseDriverCursorSize"] floatValue];
if (sizeScale > 0) {
size.rwidth() *= sizeScale;
size.rheight() *= sizeScale;
}
return size.toSize();
}
NSCursor *QCocoaCursor::convertCursor(QCursor *cursor)
{
if (!cursor)

View File

@ -50,6 +50,7 @@
#include <QtGui/qscreen.h>
#include <QtGui/private/qguiapplication_p.h> // getPixmapCursor()
#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtCore/private/qwinregistry_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qscopedpointer.h>
@ -686,6 +687,30 @@ void QWindowsCursor::setPos(const QPoint &pos)
SetCursorPos(pos.x() , pos.y());
}
/*
The standard size is 32x32, even though the cursor is actually just
16 pixels large. If a large cursor is set in the accessibility settings,
then the cursor increases with 8 pixels for each step.
*/
QSize QWindowsCursor::size() const
{
const QPair<DWORD,bool> cursorSizeSetting =
QWinRegistryKey(HKEY_CURRENT_USER, LR"(Control Panel\Cursors)")
.dwordValue(L"CursorBaseSize");
const int baseSize = screenCursorSize(m_screen).width() / 2;
if (!cursorSizeSetting.second)
return QSize(baseSize / 2, baseSize / 2);
// The registry values are dpi-independent, so we need to scale the result.
int cursorSizeValue = cursorSizeSetting.first * m_screen->logicalDpi().first
/ m_screen->logicalBaseDpi().first;
// map from registry value 32-256 to 0-14, and from there to pixels
cursorSizeValue = (cursorSizeValue - 2 * baseSize) / baseSize;
const int cursorSize = baseSize + cursorSizeValue * (baseSize / 2);
return QSize(cursorSize, cursorSize);
}
QPixmap QWindowsCursor::dragDefaultCursor(Qt::DropAction action) const
{
switch (action) {

View File

@ -113,6 +113,8 @@ public:
QPoint pos() const override;
void setPos(const QPoint &pos) override;
QSize size() const override;
static HCURSOR createPixmapCursor(QPixmap pixmap, const QPoint &hotSpot, qreal scaleFactor = 1);
static HCURSOR createPixmapCursor(const PixmapCursor &pc, qreal scaleFactor = 1) { return createPixmapCursor(pc.pixmap, pc.hotSpot, scaleFactor); }
static PixmapCursor customCursor(Qt::CursorShape cursorShape, const QPlatformScreen *screen = nullptr);

View File

@ -53,11 +53,14 @@
#endif
#include <qtextdocument.h>
#include <qdebug.h>
#include <qpa/qplatformscreen.h>
#include <qpa/qplatformcursor.h>
#include <private/qstylesheetstyle_p.h>
#ifndef QT_NO_TOOLTIP
#include <qlabel.h>
#include <QtWidgets/private/qlabel_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#include <qtooltip.h>
QT_BEGIN_NAMESPACE
@ -398,24 +401,34 @@ void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
}
#endif //QT_NO_STYLE_STYLESHEET
QRect screen = QDesktopWidgetPrivate::screenGeometry(getTipScreen(pos, w));
QPoint p = pos;
p += QPoint(2, 16);
int screenNumber = getTipScreen(pos, w);
QScreen *screen = QGuiApplication::screens().at(screenNumber);
if (screen) {
const QPlatformScreen *platformScreen = screen->handle();
const QSize cursorSize = QHighDpi::fromNativePixels(platformScreen->cursor()->size(),
platformScreen);
QPoint offset(2, cursorSize.height());
// assuming an arrow shape, we can just move to the side for very large cursors
if (cursorSize.height() > 2 * this->height())
offset = QPoint(cursorSize.width() / 2, 0);
if (p.x() + this->width() > screen.x() + screen.width())
p += offset;
QRect screenRect = screen->geometry();
if (p.x() + this->width() > screenRect.x() + screenRect.width())
p.rx() -= 4 + this->width();
if (p.y() + this->height() > screen.y() + screen.height())
if (p.y() + this->height() > screenRect.y() + screenRect.height())
p.ry() -= 24 + this->height();
if (p.y() < screen.y())
p.setY(screen.y());
if (p.x() + this->width() > screen.x() + screen.width())
p.setX(screen.x() + screen.width() - this->width());
if (p.x() < screen.x())
p.setX(screen.x());
if (p.y() + this->height() > screen.y() + screen.height())
p.setY(screen.y() + screen.height() - this->height());
if (p.y() < screenRect.y())
p.setY(screenRect.y());
if (p.x() + this->width() > screenRect.x() + screenRect.width())
p.setX(screenRect.x() + screenRect.width() - this->width());
if (p.x() < screenRect.x())
p.setX(screenRect.x());
if (p.y() + this->height() > screenRect.y() + screenRect.height())
p.setY(screenRect.y() + screenRect.height() - this->height());
}
this->move(p);
}