kde-extraapps/kdevplatform/util/activetooltip.cpp
2015-07-26 14:23:17 +03:00

357 lines
11 KiB
C++

/* This file is part of the KDE project
Copyright 2007 Vladimir Prus
Copyright 2009-2010 David Nolden
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "activetooltip.h"
#include <QPoint>
#include <QPalette>
#include <QApplication>
#include <QEvent>
#include <QMouseEvent>
#include <limits>
#include <kdebug.h>
#include <qdesktopwidget.h>
#include <qmenu.h>
#include <qstylepainter.h>
#include <qstyleoption.h>
namespace KDevelop
{
class ActiveToolTipPrivate
{
public:
uint previousDistance_;
QRect rect_;
QRegion rectExtensions_;
QList<QWeakPointer<QObject> > friendWidgets_;
int mouseOut_;
};
ActiveToolTip::ActiveToolTip(QWidget *parent, const QPoint& position)
: QWidget(parent, Qt::ToolTip), d(new ActiveToolTipPrivate)
{
Q_ASSERT(parent);
d->mouseOut_ = 0;
d->previousDistance_ = std::numeric_limits<uint>::max();
setMouseTracking(true);
d->rect_ = QRect(position, position);
d->rect_.adjust(-10, -10, 10, 10);
move(position);
QPalette p;
// adjust background color to use tooltip colors
p.setColor(backgroundRole(), p.color(QPalette::ToolTipBase));
p.setColor(QPalette::Base, p.color(QPalette::ToolTipBase));
// adjust foreground color to use tooltip colors
p.setColor(foregroundRole(), p.color(QPalette::ToolTipText));
p.setColor(QPalette::Text, p.color(QPalette::ToolTipText));
setPalette(p);
qApp->installEventFilter(this);
}
ActiveToolTip::~ActiveToolTip()
{
delete d;
}
bool ActiveToolTip::eventFilter(QObject *object, QEvent *e)
{
switch (e->type()) {
case QEvent::WindowActivate:
case QEvent::WindowDeactivate:
{
if(insideThis(object))
return false;
if(isVisible())
kDebug() << "closing because of window activation";
close();
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::Wheel:
/* If the click is within tooltip, it's fine.
Clicks outside close it. */
{
//Do not close the widget when NoFocus widgets are used
QWidget* widget = dynamic_cast<QWidget*>(object);
if(widget && widget->focusPolicy() == Qt::NoFocus)
return false;
}
if (!insideThis(object)) {
if(isVisible())
kDebug() << "closing because of click into" << object;
close();
}
// FIXME: revisit this code later.
#if 0
case QEvent::FocusIn:
case QEvent::FocusOut:
close();
break;
#endif
case QEvent::MouseMove:
{
QPoint globalPos = static_cast<QMouseEvent*>(e)->globalPos();
if (!d->rect_.isNull()
&& !d->rect_.contains(globalPos) && !d->rectExtensions_.contains(globalPos) && !insideThis(object)) {
int distance = (d->rect_.center() - static_cast<QMouseEvent*>(e)->globalPos()).manhattanLength();
// On X, when the cursor leaves the tooltip and enters
// the parent, we sometimes get some wrong Y coordinate.
// Don't know why, so wait for two out-of-range mouse
// positions before closing.
//Additional test: When the cursor has been moved towards the tooltip, don't close it.
if(distance > (int)d->previousDistance_)
{
++d->mouseOut_;
emit mouseOut();
}else
d->previousDistance_ = distance;
} else{
d->mouseOut_ = 0;
emit mouseIn();
}
if (d->mouseOut_ == 2) {
kDebug() << "closing because of mouse move";
close();
}
}
default:
break;
}
return false;
}
void ActiveToolTip::addFriendWidget(QWidget* widget)
{
d->friendWidgets_.append((QObject*)widget);
}
bool ActiveToolTip::insideThis(QObject* object)
{
while (object)
{
if(dynamic_cast<QMenu*>(object))
return true;
if (object == this || d->friendWidgets_.contains(object))
{
return true;
}
object = object->parent();
}
return false;
}
void ActiveToolTip::showEvent(QShowEvent*)
{
adjustRect();
}
void ActiveToolTip::updateMouseDistance()
{
d->previousDistance_ = (d->rect_.center() - QCursor::pos()).manhattanLength();
}
void ActiveToolTip::moveEvent(QMoveEvent* ev)
{
QWidget::moveEvent(ev);
updateMouseDistance();
}
void ActiveToolTip::resizeEvent(QResizeEvent*)
{
adjustRect();
// set mask from style
QStyleOptionFrame opt;
opt.init(this);
QStyleHintReturnMask mask;
if( style()->styleHint( QStyle::SH_ToolTip_Mask, &opt, this, &mask ) && !mask.region.isEmpty() )
{ setMask( mask.region ); }
emit resized();
updateMouseDistance();
}
void ActiveToolTip::paintEvent(QPaintEvent* event)
{
QStylePainter painter( this );
painter.setClipRegion( event->region() );
QStyleOptionFrame opt;
opt.init(this);
painter.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
}
void ActiveToolTip::addExtendRect(const QRect& rect)
{
d->rectExtensions_ += rect;
}
void ActiveToolTip::adjustRect()
{
// For tooltip widget, geometry() returns global coordinates.
QRect r = geometry();
r.adjust(-10, -10, 10, 10);
d->rect_ = r;
updateMouseDistance();
}
void ActiveToolTip::setBoundingGeometry(const QRect& geometry) {
d->rect_ = geometry;
d->rect_.adjust(-10, -10, 10, 10);
}
namespace {
typedef QMultiMap<float, QPair<QWeakPointer<ActiveToolTip>, QString> > ToolTipPriorityMap;
static ToolTipPriorityMap registeredToolTips;
ActiveToolTipManager manager;
QWidget* masterWidget(QWidget* w) {
while(w && w->parent() && qobject_cast<QWidget*>(w->parent()))
w = qobject_cast<QWidget*>(w->parent());
return w;
}
}
void ActiveToolTipManager::doVisibility() {
bool exclusive = false;
int lastBottomPosition = -1;
int lastLeftPosition = -1;
QRect fullGeometry; //Geometry of all visible tooltips together
for(ToolTipPriorityMap::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) {
QWeakPointer< ActiveToolTip > w = (*it).first;
if(w) {
if(exclusive) {
(w.data())->hide();
}else{
QRect geom = (w.data())->geometry();
if((w.data())->geometry().top() < lastBottomPosition) {
geom.moveTop(lastBottomPosition);
}
if(lastLeftPosition != -1)
geom.moveLeft(lastLeftPosition);
(w.data())->setGeometry(geom);
// (w.data())->show();
lastBottomPosition = (w.data())->geometry().bottom();
lastLeftPosition = (w.data())->geometry().left();
if(it == registeredToolTips.constBegin())
fullGeometry = (w.data())->geometry();
else
fullGeometry = fullGeometry.united((w.data())->geometry());
}
if(it.key() == 0) {
exclusive = true;
}
}
}
if(!fullGeometry.isEmpty()) {
QRect oldFullGeometry = fullGeometry;
QRect screenGeometry = QApplication::desktop()->screenGeometry(fullGeometry.topLeft());
if(fullGeometry.bottom() > screenGeometry.bottom()) {
//Move up, avoiding the mouse-cursor
fullGeometry.moveBottom(fullGeometry.top()-10);
if(fullGeometry.adjusted(-20, -20, 20, 20).contains(QCursor::pos()))
fullGeometry.moveBottom(QCursor::pos().y() - 20);
}
if(fullGeometry.right() > screenGeometry.right()) {
//Move to left, avoiding the mouse-cursor
fullGeometry.moveRight(fullGeometry.left()-10);
if(fullGeometry.adjusted(-20, -20, 20, 20).contains(QCursor::pos()))
fullGeometry.moveRight(QCursor::pos().x() - 20);
}
// Now fit this to screen
if (fullGeometry.left() < 0) {
fullGeometry.setLeft(0);
}
if (fullGeometry.top() < 0) {
fullGeometry.setTop(0);
}
QPoint offset = fullGeometry.topLeft() - oldFullGeometry.topLeft();
if(!offset.isNull()) {
for(ToolTipPriorityMap::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it)
if((*it).first) {
(*it).first.data()->move((*it).first.data()->pos() + offset);
}
}
}
//Set bounding geometry, and remove old tooltips
for(ToolTipPriorityMap::iterator it = registeredToolTips.begin(); it != registeredToolTips.end(); ) {
if(!(*it).first) {
it = registeredToolTips.erase(it);
}else{
(*it).first.data()->setBoundingGeometry(fullGeometry);
++it;
}
}
//Final step: Show tooltips
for(ToolTipPriorityMap::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) {
if(it->first.data() && masterWidget(it->first.data())->isActiveWindow())
(*it).first.data()->show();
if(exclusive)
break;
}
}
void ActiveToolTip::showToolTip(KDevelop::ActiveToolTip* tooltip, float priority, QString uniqueId) {
if(!uniqueId.isEmpty()) {
for(QMap< float, QPair< QWeakPointer< ActiveToolTip >, QString > >::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) {
if((*it).second == uniqueId)
delete (*it).first.data();
}
}
registeredToolTips.insert(priority, qMakePair(QWeakPointer<KDevelop::ActiveToolTip>(tooltip), uniqueId));
connect(tooltip, SIGNAL(resized()), &manager, SLOT(doVisibility()));
QMetaObject::invokeMethod(&manager, "doVisibility", Qt::QueuedConnection);
}
void ActiveToolTip::closeEvent(QCloseEvent* event)
{
QWidget::closeEvent(event);
deleteLater();
}
}
#include "moc_activetooltip.cpp"