/* 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 #include #include #include #include #include #include #include #include #include #include namespace KDevelop { class ActiveToolTipPrivate { public: uint previousDistance_; QRect rect_; QRegion rectExtensions_; QList > 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::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(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(e)->globalPos(); if (!d->rect_.isNull() && !d->rect_.contains(globalPos) && !d->rectExtensions_.contains(globalPos) && !insideThis(object)) { int distance = (d->rect_.center() - static_cast(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(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, QString> > ToolTipPriorityMap; static ToolTipPriorityMap registeredToolTips; ActiveToolTipManager manager; QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(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(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"