diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -16,6 +16,7 @@ ksqueezedtextlabelautotest.cpp ktimecomboboxtest.cpp ktooltipwidgettest.cpp + ktooltippositiontest.cpp kmessagewidgetautotest.cpp kpagedialogautotest.cpp kpassworddialogautotest.cpp diff --git a/autotests/ktooltippositiontest.h b/autotests/ktooltippositiontest.h new file mode 100644 --- /dev/null +++ b/autotests/ktooltippositiontest.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Michael Heidelbach + * + * This program 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 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 this program. If not, see . + */ + +#ifndef KTOOLTIPPOSITIONTEST_H +#define KTOOLTIPPOSITIONTEST_H + +#include +#include + +class QLabel; + +class KTooltipPositionTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + void init(); + void cleanup(); + + void shouldNotObscureTarget_data(); + void shouldNotObscureTarget(); + +private: + QRect m_screenGeometry; + QWidget* m_container; + QLabel* m_target; + int m_margin; +}; + +#endif // KTOOLTIPPOSITIONTEST_H diff --git a/autotests/ktooltippositiontest.cpp b/autotests/ktooltippositiontest.cpp new file mode 100644 --- /dev/null +++ b/autotests/ktooltippositiontest.cpp @@ -0,0 +1,152 @@ +/* + * Copyright 2018 Michael Heidelbach + * + * This program 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 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 this program. If not, see . + */ + +#include "ktooltippositiontest.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QTEST_MAIN(KTooltipPositionTest) + +void KTooltipPositionTest::initTestCase() +{ + m_screenGeometry = QGuiApplication::primaryScreen()->geometry(); +} + +void KTooltipPositionTest::init() +{ + m_container = new QWidget(); + m_target = new QLabel(QStringLiteral("dummy file")); + QHBoxLayout* layout = new QHBoxLayout(m_container); + layout->addWidget(m_target); + m_margin = m_container->style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth); +} + +void KTooltipPositionTest::cleanup() +{ + delete m_container; + m_container = 0; +} + +void KTooltipPositionTest::shouldNotObscureTarget_data() +{ + QTest::addColumn("position"); + QTest::addColumn("content"); + + QMap positions; + positions.insert(QStringLiteral("topleft"), QPoint(10, 10)); + positions.insert(QStringLiteral("topright"), QPoint(-10, 10)); + positions.insert(QStringLiteral("bottomright"), QPoint(-10, -10)); + positions.insert(QStringLiteral("bottomleft"), QPoint(10, -10)); + positions.insert(QStringLiteral("centered") + , QPoint(m_screenGeometry.width() / 2, m_screenGeometry.height() / 2)); + + QMapIterator i(positions); + while (i.hasNext()) { + i.next(); + //FIXME: Compose names w/o compiler warning -Wformat-security + QTest::addRow(QStringLiteral("small/%1").arg(i.key()).toLatin1().constData()) + << i.value() + << QStringLiteral("dummy text"); + + QTest::addRow(QStringLiteral("multiline/%1").arg(i.key()).toLatin1().constData()) + << i.value() + << QStringLiteral("dummy text\nLine 1\nLine 2\nLine 3"); + + QTest::addRow(QStringLiteral("one long line/%1").arg(i.key()).toLatin1().constData()) + << i.value() + << QStringLiteral( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec felis sed elit auctor lobortis non a urna. Quisque non posuere mauris. Suspendisse potenti. In diam leo, lobortis at placerat nec, sagittis at tortor. Pellentesque scelerisque enim vel elementum scelerisque. Integer eget lectus vitae lorem pulvinar hendrerit. Suspendisse auctor sapien vel semper porta. Vestibulum fringilla aliquet tincidunt. Maecenas mollis mauris et erat viverra mollis. Proin suscipit felis nisi, a dapibus est hendrerit euismod. Suspendisse quis faucibus quam. Fusce eu cursus magna, et egestas purus. Duis enim sapien, feugiat id facilisis non, rhoncus ut lectus. Aliquam at nisi vel ligula interdum ultricies. Donec condimentum ante quam, eu congue lectus pulvinar in. Cras interdum, neque quis fermentum consequat, lectus tellus eleifend turpis." + ); + + if (m_screenGeometry.width() >= 1600 && m_screenGeometry.width() >= 900) { + QTest::addRow(QStringLiteral("super large/%1").arg(i.key()).toLatin1().constData()) + << i.value() + << QStringLiteral( + "dummy 0 text\nLine 1\nLine 2\nLine 3" + "dummy 1 text\nLine 1\nLine 2\nLine 3" + "dummy 2 text\nLine 1\nLine 2\nLine 3" + "dummy 3 text\nLine 1\nLine 2\nLine 3" + "dummy 4 text\nLine 1\nLine 2\nLine 3" + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec felis sed elit auctor lobortis non a urna. Quisque non posuere mauris. Suspendisse potenti. In diam leo, lobortis at placerat nec, sagittis at tortor. Pellentesque scelerisque enim vel elementum scelerisque. Integer eget lectus vitae lorem pulvinar hendrerit. Suspendisse auctor sapien vel semper porta. Vestibulum fringilla aliquet tincidunt. Maecenas mollis mauris et erat viverra mollis. Proin suscipit felis nisi, a dapibus est hendrerit euismod. Suspendisse quis faucibus quam. Fusce eu cursus magna, et egestas purus. Duis enim sapien, feugiat id facilisis non, rhoncus ut lectus. Aliquam at nisi vel ligula interdum ultricies. Donec condimentum ante quam, eu congue lectus pulvinar in. Cras interdum, neque quis fermentum consequat, lectus tellus eleifend turpis." + ); + } + } +} + +void KTooltipPositionTest::shouldNotObscureTarget() +{ + QFETCH(QPoint, position); + QFETCH(QString, content); + + m_container->adjustSize(); + m_container->move( + position.x() >= 0 + ? position.x() + : m_screenGeometry.right() + position.x() - m_container->width() + , position.y() >= 0 + ? position.y() + : m_screenGeometry.bottom() + position.y() - m_container->height() + ); + m_container->show(); + QVERIFY(QTest::qWaitForWindowActive(m_container)); + QVERIFY(m_target->isVisible()); + + QRect targetRect = QRect(m_target->frameGeometry()); + targetRect.moveTo(m_target->mapToGlobal(QPoint(0, 0))); + + QLabel* contentWidget(new QLabel(content)); + contentWidget->setMaximumSize(m_screenGeometry.width() - 20, m_screenGeometry.height() - 20); + + QWindow* transientParent(m_container->windowHandle()); + KToolTipWidget tooltip; + tooltip.showBelow(targetRect, contentWidget, transientParent); + QVERIFY(QTest::qWaitForWindowActive(&tooltip)); + QRect tooltipRect(tooltip.frameGeometry()); + + //TODO: Remove before landing + qDebug() << "target: " << targetRect; + qDebug() << "tooltip:" << tooltipRect; + + QVERIFY2(!tooltipRect.intersects(targetRect), "Target obscured"); + QVERIFY2(tooltipRect.intersected(m_screenGeometry) == tooltipRect, "Displayed offscreen"); + + // Check margins + if (tooltipRect.bottom() < targetRect.top() ) { + QCOMPARE(m_margin, targetRect.top() - tooltipRect.bottom()); + } else if (tooltipRect.top() > targetRect.bottom()) { + QCOMPARE(m_margin, tooltipRect.top() - targetRect.bottom()); + } else if (tooltipRect.right() < targetRect.left()) { + QCOMPARE(m_margin, targetRect.left() - tooltipRect.right()); + } else if (tooltipRect.left() > targetRect.right() ) { + QCOMPARE(m_margin, tooltipRect.left() - targetRect.right()); + } else { + QFAIL("Test is wrong"); + } + + tooltip.hide(); +} diff --git a/src/ktooltipwidget.cpp b/src/ktooltipwidget.cpp --- a/src/ktooltipwidget.cpp +++ b/src/ktooltipwidget.cpp @@ -119,15 +119,16 @@ // It must be assured that: // - the content is fully visible // - the content is not drawn inside rect - - const QSize size = content->sizeHint(); - const int margin = q->style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth); + q->adjustSize(); + const QSize size = q->sizeHint(); const QRect screenGeometry = screen->geometry(); - + const int margin = q->style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth); + const bool hasRoomToLeft = (rect.left() - size.width() - margin >= screenGeometry.left()); const bool hasRoomToRight = (rect.right() + size.width() + margin <= screenGeometry.right()); const bool hasRoomAbove = (rect.top() - size.height() - margin >= screenGeometry.top()); const bool hasRoomBelow = (rect.bottom() + size.height() + margin <= screenGeometry.bottom()); + if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) { return QPoint(); } @@ -137,19 +138,20 @@ if (hasRoomBelow || hasRoomAbove) { x = qMax(screenGeometry.left(), rect.center().x() - size.width() / 2); if (x + size.width() >= screenGeometry.right()) { - x = screenGeometry.right() - size.width() + 1; + // Disallow negative x. Happens when rect is wider than screen. + x = qMax(screenGeometry.left(), screenGeometry.right() - size.width() + 1); } if (hasRoomBelow) { y = rect.bottom() + margin; } else { - y = rect.top() - size.height() - margin; + y = rect.top() - size.height() - margin + 1; } } else { Q_ASSERT(hasRoomToLeft || hasRoomToRight); if (hasRoomToRight) { x = rect.right() + margin; } else { - x = rect.left() - size.width() - margin; + x = rect.left() - size.width() - margin + 1; } // Put the tooltip at the bottom of the screen. The x-coordinate has already // been adjusted, so that no overlapping with rect occurs.