File indexing completed on 2024-04-28 04:18:50

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "fullscreenbar.h"
0023 
0024 // Qt
0025 #include <QAction>
0026 #include <QApplication>
0027 #include <QBitmap>
0028 #include <QEvent>
0029 #include <QLayout>
0030 #include <QMouseEvent>
0031 #include <QScreen>
0032 #include <QTimeLine>
0033 #include <QTimer>
0034 #include <QToolButton>
0035 
0036 // KF
0037 #include <KLocalizedString>
0038 #include <chrono>
0039 
0040 using namespace std::chrono_literals;
0041 
0042 // Local
0043 
0044 namespace Gwenview
0045 {
0046 static const int SLIDE_DURATION = 150;
0047 static const int AUTO_HIDE_CURSOR_TIMEOUT = 1000;
0048 
0049 // How long before the bar slide out after switching to fullscreen
0050 static const int INITIAL_HIDE_TIMEOUT = 2000;
0051 
0052 // Do not slide bar out if mouse is less than this amount of pixels below bar, to
0053 // prevent accidental slide outs
0054 static const int EXTRA_BAR_HEIGHT = 20;
0055 
0056 struct FullScreenBarPrivate {
0057     FullScreenBar *q = nullptr;
0058     QTimeLine *mTimeLine = nullptr;
0059     QTimer *mAutoHideCursorTimer = nullptr;
0060     bool mAutoHidingEnabled;
0061     bool mEdgeTriggerEnabled;
0062     QTimer *mInitialHideTimer = nullptr;
0063 
0064     void startTimeLine()
0065     {
0066         if (mTimeLine->state() != QTimeLine::Running) {
0067             mTimeLine->start();
0068         }
0069     }
0070 
0071     void hideCursor()
0072     {
0073         QBitmap empty(32, 32);
0074         empty.clear();
0075         const QCursor blankCursor(empty, empty);
0076         QApplication::setOverrideCursor(blankCursor);
0077     }
0078 
0079     /**
0080      * Returns the rectangle in which the mouse must enter to trigger bar
0081      * sliding. The rectangle is in global coords.
0082      */
0083     QRect slideInTriggerRect() const
0084     {
0085         const QScreen *screen = QGuiApplication::screenAt(QCursor::pos());
0086 
0087         if (!screen) {
0088             return {};
0089         }
0090 
0091         QRect rect = screen->geometry();
0092         // Take parent widget position into account because it may not be at
0093         // the top of the screen, for example when the save bar warning is
0094         // shown.
0095         rect.setHeight(q->parentWidget()->y() + q->height() + EXTRA_BAR_HEIGHT);
0096         return rect;
0097     }
0098 
0099     bool shouldHide() const
0100     {
0101         Q_ASSERT(q->parentWidget());
0102 
0103         if (!mAutoHidingEnabled) {
0104             return false;
0105         }
0106         if (slideInTriggerRect().contains(QCursor::pos())) {
0107             return false;
0108         }
0109         if (QApplication::activePopupWidget()) {
0110             return false;
0111         }
0112         // Do not hide if a button is down, which can happen when we are
0113         // using a scroll bar.
0114         if (QApplication::mouseButtons() != Qt::NoButton) {
0115             return false;
0116         }
0117         return true;
0118     }
0119 };
0120 
0121 FullScreenBar::FullScreenBar(QWidget *parent)
0122     : QFrame(parent)
0123     , d(new FullScreenBarPrivate)
0124 {
0125     d->q = this;
0126     d->mAutoHidingEnabled = true;
0127     d->mEdgeTriggerEnabled = true;
0128     setObjectName(QStringLiteral("fullScreenBar"));
0129 
0130     d->mTimeLine = new QTimeLine(SLIDE_DURATION, this);
0131     connect(d->mTimeLine, &QTimeLine::valueChanged, this, &FullScreenBar::moveBar);
0132 
0133     d->mAutoHideCursorTimer = new QTimer(this);
0134     d->mAutoHideCursorTimer->setInterval(AUTO_HIDE_CURSOR_TIMEOUT);
0135     d->mAutoHideCursorTimer->setSingleShot(true);
0136     connect(d->mAutoHideCursorTimer, &QTimer::timeout, this, &FullScreenBar::slotAutoHideCursorTimeout);
0137 
0138     d->mInitialHideTimer = new QTimer(this);
0139     d->mInitialHideTimer->setInterval(INITIAL_HIDE_TIMEOUT);
0140     d->mInitialHideTimer->setSingleShot(true);
0141     connect(d->mInitialHideTimer, &QTimer::timeout, this, &FullScreenBar::slideOut);
0142 
0143     hide();
0144 }
0145 
0146 FullScreenBar::~FullScreenBar()
0147 {
0148     delete d;
0149 }
0150 
0151 QSize FullScreenBar::sizeHint() const
0152 {
0153     QSize sh = QFrame::sizeHint();
0154     if (!layout()) {
0155         return sh;
0156     }
0157 
0158     if (layout()->expandingDirections() & Qt::Horizontal) {
0159         sh.setWidth(parentWidget()->width());
0160     }
0161     return sh;
0162 }
0163 
0164 void FullScreenBar::moveBar(qreal value)
0165 {
0166     move(0, -height() + int(value * height()));
0167 
0168     // For some reason, if Gwenview is started with command line options to
0169     // start a slideshow, the bar might end up below the view. Calling raise()
0170     // here fixes it.
0171     raise();
0172 }
0173 
0174 void FullScreenBar::setActivated(bool activated)
0175 {
0176     if (activated) {
0177         // Delay installation of event filter because switching to fullscreen
0178         // cause a few window adjustments, which seems to generate unwanted
0179         // mouse events, which cause the bar to slide in.
0180         QTimer::singleShot(500ms, this, &FullScreenBar::delayedInstallEventFilter);
0181 
0182         adjustSize();
0183 
0184         // Make sure the widget is visible on start
0185         move(0, 0);
0186         raise();
0187         show();
0188     } else {
0189         qApp->removeEventFilter(this);
0190         hide();
0191         d->mAutoHideCursorTimer->stop();
0192         QApplication::restoreOverrideCursor();
0193     }
0194 }
0195 
0196 void FullScreenBar::delayedInstallEventFilter()
0197 {
0198     qApp->installEventFilter(this);
0199     if (d->shouldHide()) {
0200         d->mInitialHideTimer->start();
0201         d->hideCursor();
0202     }
0203 }
0204 
0205 void FullScreenBar::slotAutoHideCursorTimeout()
0206 {
0207     if (d->shouldHide()) {
0208         d->hideCursor();
0209     } else {
0210         d->mAutoHideCursorTimer->start();
0211     }
0212 }
0213 
0214 void FullScreenBar::slideOut()
0215 {
0216     d->mInitialHideTimer->stop();
0217     d->mTimeLine->setDirection(QTimeLine::Backward);
0218     d->startTimeLine();
0219 }
0220 
0221 void FullScreenBar::slideIn()
0222 {
0223     d->mInitialHideTimer->stop();
0224     d->mTimeLine->setDirection(QTimeLine::Forward);
0225     d->startTimeLine();
0226 }
0227 
0228 bool FullScreenBar::eventFilter(QObject *object, QEvent *event)
0229 {
0230     if (event->type() == QEvent::MouseMove) {
0231         QApplication::restoreOverrideCursor();
0232         d->mAutoHideCursorTimer->start();
0233         if (y() == 0) {
0234             if (d->shouldHide()) {
0235                 slideOut();
0236             }
0237         } else {
0238             auto mouseEvent = static_cast<QMouseEvent *>(event);
0239             if (d->mEdgeTriggerEnabled && mouseEvent->buttons() == 0 && d->slideInTriggerRect().contains(QCursor::pos())) {
0240                 slideIn();
0241             }
0242         }
0243         return false;
0244     }
0245 
0246     if (event->type() == QEvent::MouseButtonRelease) {
0247         // This can happen if user released the mouse after using a scrollbar
0248         // in the content (the bar does not hide while a button is down)
0249         if (y() == 0 && d->shouldHide()) {
0250             slideOut();
0251         }
0252         return false;
0253     }
0254 
0255     // Filtering message on tooltip text for CJK to remove accelerators.
0256     // Quoting ktoolbar.cpp:
0257     // """
0258     // CJK languages use more verbose accelerator marker: they add a Latin
0259     // letter in parenthesis, and put accelerator on that. Hence, the default
0260     // removal of ampersand only may not be enough there, instead the whole
0261     // parenthesis construct should be removed. Use KLocale's method to do this.
0262     // """
0263     if (event->type() == QEvent::Show || event->type() == QEvent::Paint) {
0264         auto button = qobject_cast<QToolButton *>(object);
0265         if (button && !button->actions().isEmpty()) {
0266             QAction *action = button->actions().constFirst();
0267             const QString toolTip = KLocalizedString::removeAcceleratorMarker(action->toolTip());
0268             // Filtering message requested by translators (scripting).
0269             button->setToolTip(i18nc("@info:tooltip of custom toolbar button", "%1", toolTip));
0270         }
0271     }
0272 
0273     return false;
0274 }
0275 
0276 void FullScreenBar::setAutoHidingEnabled(bool value)
0277 {
0278     d->mAutoHidingEnabled = value;
0279 }
0280 
0281 void FullScreenBar::setEdgeTriggerEnabled(bool value)
0282 {
0283     d->mEdgeTriggerEnabled = value;
0284 }
0285 
0286 } // namespace
0287 
0288 #include "moc_fullscreenbar.cpp"