File indexing completed on 2022-11-23 11:29:39

0001 /*
0002  * Copyright (C) 2008 by Konstantin Heil <konst.heil@stud.uni-heidelberg.de>
0003  * Copyright (C) 2011 Geoffry Song <goffrie@gmail.com>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Lesser General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2.1 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Lesser General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Lesser General Public
0016  * License along with this library; if not, write to the Free Software
0017  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0018  */
0019 
0020 #include "tooltipmanager.h"
0021 
0022 #include "ktooltip.h"
0023 #include "contacttooltip.h"
0024 #include "persontooltip.h"
0025 
0026 #include <QRect>
0027 #include <QTimer>
0028 #include <QPainter>
0029 #include <QScrollBar>
0030 #include <QApplication>
0031 #include <QDesktopWidget>
0032 #include <QAbstractItemView>
0033 
0034 #ifdef Q_WS_X11
0035 #include <QX11Info>
0036 #endif
0037 
0038 #include <KColorScheme>
0039 
0040 #include <KTp/types.h>
0041 
0042 class ToolTipManager::Private
0043 {
0044 public:
0045     Private() :
0046         view(0),
0047         timer(0)
0048         { }
0049 
0050     QAbstractItemView *view;
0051     QTimer            *timer;
0052     QPersistentModelIndex        item;
0053     QRect              itemRect;
0054 };
0055 
0056 ToolTipManager::ToolTipManager(QAbstractItemView *parent)
0057     : QObject(parent)
0058     , d(new ToolTipManager::Private)
0059 {
0060     d->view = parent;
0061 
0062     connect(parent, SIGNAL(viewportEntered()), this, SLOT(hideToolTip()));
0063     connect(parent, SIGNAL(entered(QModelIndex)), this, SLOT(requestToolTip(QModelIndex)));
0064 
0065     d->timer = new QTimer(this);
0066     d->timer->setSingleShot(true);
0067     connect(d->timer, SIGNAL(timeout()), this, SLOT(prepareToolTip()));
0068 
0069     // When the mousewheel is used, the items don't get a hovered indication
0070     // (Qt-issue #200665). To assure that the tooltip still gets hidden,
0071     // the scrollbars are observed.
0072     connect(parent->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(hideToolTip()));
0073     connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(hideToolTip()));
0074 
0075     d->view->viewport()->installEventFilter(this);
0076 }
0077 
0078 ToolTipManager::~ToolTipManager()
0079 {
0080     delete d;
0081 }
0082 
0083 bool ToolTipManager::eventFilter(QObject *watched, QEvent *event)
0084 {
0085     if (watched == d->view->viewport()) {
0086         switch (event->type()) {
0087             case QEvent::WindowDeactivate:
0088                 hideToolTip();
0089                 break;
0090             case QEvent::Leave:
0091                 hideToolTip();
0092                 break;
0093             case QEvent::MouseButtonPress:
0094                 hideToolTip();
0095                 break;
0096             case QEvent::ToolTip:
0097                 return true;
0098             default:
0099                 break;
0100         }
0101     } else if (watched == KToolTip::currentTip()) {
0102         if (event->type() == QEvent::Leave) {
0103             hideToolTip();
0104         }
0105         return false;
0106     }
0107 
0108     return QObject::eventFilter(watched, event);
0109 }
0110 
0111 void ToolTipManager::requestToolTip(const QModelIndex &index)
0112 {
0113     // only request a tooltip for the name column and when no selection or
0114     // drag & drop operation is done (indicated by the left mouse button)
0115     if (!(QApplication::mouseButtons() & Qt::LeftButton) && index.isValid()) {
0116         KToolTip::hideTip();
0117 
0118         QRect rect = d->view->visualRect(index);
0119         // use 0 as the left x coordinate to make sure the tooltip does not cover the tree view controls
0120         d->itemRect = QRect(d->view->viewport()->mapToGlobal(QPoint(0, rect.topLeft().y())),
0121                             d->view->viewport()->mapToGlobal(rect.bottomRight()));
0122         d->item = index;
0123         d->timer->start(300);
0124     } else {
0125         hideToolTip();
0126     }
0127 }
0128 
0129 void ToolTipManager::hideToolTip()
0130 {
0131     if ( KToolTip::currentTip() && KToolTip::currentTip()->geometry().contains(QCursor::pos()) ) return;
0132     d->timer->stop();
0133     KToolTip::hideTip();
0134 }
0135 
0136 void ToolTipManager::prepareToolTip()
0137 {
0138     if (!d->view->isActiveWindow()) {
0139         return;
0140     }
0141 
0142     if (d->item.isValid()) {
0143         showToolTip(d->item);
0144     }
0145 }
0146 
0147 void ToolTipManager::showToolTip(const QModelIndex &menuItem)
0148 {
0149     if (QApplication::mouseButtons() & Qt::LeftButton || !menuItem.isValid()) {
0150         return;
0151     }
0152 
0153     if (menuItem.data(KTp::RowTypeRole).toUInt() != KTp::ContactRowType
0154         && menuItem.data(KTp::RowTypeRole).toUInt() != KTp::PersonRowType) {
0155         return;
0156     }
0157 
0158     QWidget *tip = KToolTip::createTipWindow(createTipContent(menuItem));
0159 
0160     // calculate the x- and y-position of the tooltip
0161     const QSize size = tip->size();
0162     const QRect desktop = QApplication::desktop()->screenGeometry( QCursor::pos() );
0163 
0164     // d->itemRect defines the area of the item, where the tooltip should be
0165     // shown. Per default the tooltip is shown to the right
0166     // If the tooltip content exceeds the desktop borders, it must be assured that:
0167     // - the content is fully visible, if possible
0168     // - the content is not drawn inside d->itemRect
0169     const int margin = 3;
0170     const bool hasRoomToLeft  = (d->itemRect.left()   - size.width()  - margin >= desktop.left());
0171     const bool hasRoomToRight = (d->itemRect.right()  + size.width()  + margin <= desktop.right());
0172     const bool hasRoomAbove   = (d->itemRect.top()    - size.height() - margin >= desktop.top());
0173     const bool hasRoomBelow   = (d->itemRect.bottom() + size.height() + margin <= desktop.bottom());
0174     if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) {
0175         delete tip;
0176         tip = 0;
0177         return;
0178     }
0179 
0180     int x = 0;
0181     int y = 0;
0182 
0183     if (hasRoomToLeft || hasRoomToRight) {
0184         x = hasRoomToRight ? d->itemRect.right() + margin : d->itemRect.left() - size.width() - margin;
0185         y = qMin(qMax(desktop.top() + margin, d->itemRect.center().y() - size.height() / 2), desktop.bottom() - size.height() - margin);
0186     } else {
0187         Q_ASSERT(hasRoomBelow || hasRoomAbove);
0188         y = hasRoomBelow ? d->itemRect.bottom() + margin : d->itemRect.top() - size.height() - margin;
0189         x = qMin(qMax(desktop.left() + margin, d->itemRect.center().x() - size.width() / 2), desktop.right() - size.width() - margin);
0190     }
0191 
0192     tip->installEventFilter(this);
0193     // the ownership of tip is transferred to KToolTip
0194     KToolTip::showTip(QPoint(x, y), tip);
0195 }
0196 
0197 QWidget * ToolTipManager::createTipContent(const QModelIndex &index)
0198 {
0199     if (index.data(KTp::RowTypeRole).toUInt() == KTp::PersonRowType) {
0200         return new PersonToolTip(index);
0201     } else {
0202         return new ContactToolTip(index);
0203     }
0204 }