diff --git a/CMakeLists.txt b/CMakeLists.txt index b3e36f1cb..f7bf66605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,8 @@ set(plasma_LIB_SRCS svg.cpp theme.cpp toolbox.cpp + tooltipmanager.cpp + tooltip.cpp uiloader.cpp version.cpp view.cpp @@ -140,7 +142,9 @@ set(plasma_LIB_INCLUDES servicejob.h svg.h theme.h + tooltipmanager.h uiloader.h + tooltipmanager.h version.h view.h) @@ -225,6 +229,7 @@ includes/ServiceJob includes/SignalPlotter includes/Svg includes/TextEdit +includes/ToolTipManager includes/Theme includes/UiLoader includes/View diff --git a/applet.cpp b/applet.cpp index 3316d28b9..13a7aa3cd 100644 --- a/applet.cpp +++ b/applet.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include #include @@ -76,6 +75,7 @@ #include "plasma/view.h" #include "plasma/widgets/label.h" #include "plasma/widgets/pushbutton.h" +#include "plasma/tooltipmanager.h" //#define DYNAMIC_SHADOWS namespace Plasma @@ -434,7 +434,8 @@ QRect Applet::mapToView(const QGraphicsView *view, const QRectF &rect) const QPoint Applet::popupPosition(const QSize &s) const { - QGraphicsView *v = view(); + return ToolTipManager::popupPosition(this,s); + /*QGraphicsView *v = view(); Q_ASSERT(v); QPoint pos = v->mapFromScene(scenePos()); @@ -482,7 +483,7 @@ QPoint Applet::popupPosition(const QSize &s) const } pos.rx() = qMax(0, pos.rx()); - return pos; + return pos;*/ } void Applet::updateConstraints(Plasma::Constraints constraints) @@ -1430,6 +1431,49 @@ QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value) return QGraphicsWidget::itemChange(change, value); } +bool Applet::sceneEvent(QEvent *event) +{ + switch (event->type()) { + case QEvent::GraphicsSceneHoverMove: + // If the tooltip isn't visible, run through showing the tooltip again + // so that it only becomes visible after a stationary hover + if (Plasma::ToolTipManager::self()->isVisible(this)) { + break; + } + + case QEvent::GraphicsSceneHoverEnter: + { + // Check that there is a tooltip to show + if (!Plasma::ToolTipManager::self()->hasToolTip(this)) { + break; + } + + // If the mouse is in the widget's area at the time that it is being + // created the widget can receive a hover event before it is fully + // initialized, in which case view() will return 0. + QGraphicsView *parentView = view(); + if (parentView) { + Plasma::ToolTipManager::self()->showToolTip(this); + } + + break; + } + + case QEvent::GraphicsSceneHoverLeave: + Plasma::ToolTipManager::self()->delayedHideToolTip(); + break; + + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneWheel: + Plasma::ToolTipManager::self()->hideToolTip(this); + + default: + break; + } + + return QGraphicsWidget::sceneEvent(event); +} + QPainterPath Applet::shape() const { if (d->script) { diff --git a/applet.h b/applet.h index 295073b38..9b1e80b7a 100644 --- a/applet.h +++ b/applet.h @@ -728,6 +728,11 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget */ QVariant itemChange(GraphicsItemChange change, const QVariant &value); + /** + * Reimplemented from QGraphicsItem + */ + bool sceneEvent(QEvent *event); + /** * Reimplemented from QGraphicsItem */ diff --git a/includes/ToolTipManager b/includes/ToolTipManager new file mode 100644 index 000000000..54f27f0a0 --- /dev/null +++ b/includes/ToolTipManager @@ -0,0 +1 @@ +#include "../../plasma/tooltipmanager.h" diff --git a/tooltip.cpp b/tooltip.cpp new file mode 100644 index 000000000..e09bcc553 --- /dev/null +++ b/tooltip.cpp @@ -0,0 +1,268 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright (C) 2008 by Alexis Ménard + * + * This program 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, or + * (at your option) any later version. + + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "tooltip_p.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#endif + +#include +#include +#include +#include +#include + +#ifdef Q_WS_X11 +#include +#include +#endif + +#include "plasma/plasma.h" + +namespace Plasma { + +class ToolTipPrivate +{ + public: + ToolTipPrivate() + : label(0) + , imageLabel(0) + , preview(0) + , windowToPreview(0) + { } + + QLabel *label; + QLabel *imageLabel; + WindowPreview *preview; + WId windowToPreview; + PanelSvg *background; + +}; + +void ToolTip::showEvent(QShowEvent *e) +{ + QWidget::showEvent(e); + d->preview->setInfo(); +} + +void ToolTip::mouseReleaseEvent(QMouseEvent* event) +{ + if (rect().contains(event->pos())) { + hide(); + } +} + +ToolTip::ToolTip() + : QWidget(0) + , d( new ToolTipPrivate ) +{ + setWindowFlags(Qt::ToolTip); + QGridLayout *l = new QGridLayout; + d->preview = new WindowPreview; + d->label = new QLabel; + d->label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + d->label->setWordWrap(true); + d->imageLabel = new QLabel; + d->imageLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + d->background = new PanelSvg(this); + connect(d->background, SIGNAL(repaintNeeded()), this, SLOT(update())); + + l->addWidget(d->preview, 0, 0, 1, 2); + l->addWidget(d->imageLabel, 1, 0); + l->addWidget(d->label, 1, 1); + setLayout(l); + +} + +ToolTip::~ToolTip() +{ + delete d; +} + +void ToolTip::setData(const ToolTipManager::ToolTipData &data) +{ + //reset our size + d->label->setText("" + data.mainText + "
" + data.subText + "
"); + d->imageLabel->setPixmap(data.image); + d->windowToPreview = data.windowToPreview; + d->preview->setWindowId( d->windowToPreview ); + + if (isVisible()) { + resize(sizeHint()); + } +} + +void ToolTip::prepareShowing() +{ + if( d->windowToPreview != 0 ) { + // show/hide the preview area + d->preview->show(); + kDebug()<<"Show Preview of window"; + } else { + d->preview->hide(); + } + layout()->activate(); + + resize(sizeHint()); + + if (isVisible()) { + d->preview->setInfo(); + } else { + show(); + } +} + +void ToolTip::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + d->background->resizePanel(size()); + + setMask(d->background->mask()); +} + +void ToolTip::paintEvent(QPaintEvent *e) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setClipRect(e->rect()); + painter.setCompositionMode(QPainter::CompositionMode_Source ); + painter.fillRect(rect(), Qt::transparent); + + d->background->paintPanel(&painter, rect()); +} + +void ToolTip::updateTheme() +{ + d->background->setImagePath("widgets/tooltip"); + d->background->setEnabledBorders(PanelSvg::AllBorders); + + const int topHeight = d->background->marginSize(Plasma::TopMargin); + const int leftWidth = d->background->marginSize(Plasma::LeftMargin); + const int rightWidth = d->background->marginSize(Plasma::RightMargin); + const int bottomHeight = d->background->marginSize(Plasma::BottomMargin); + setContentsMargins(leftWidth, topHeight, rightWidth, bottomHeight); + + // Make the tooltip use Plasma's colorscheme + QPalette plasmaPalette = QPalette(); + plasmaPalette.setColor(QPalette::Window, Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor)); + plasmaPalette.setColor(QPalette::WindowText, Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor)); + setAutoFillBackground(true); + setPalette(plasmaPalette); +} + +// A widget which reserves area for window preview and sets hints on the toplevel +// tooltip widget that tells KWin to render the preview in this area. This depends +// on KWin's TaskbarThumbnail compositing effect (which is here detected). + +void WindowPreview::setWindowId(WId w) +{ + if (!previewsAvailable()) { + id = 0; + return; + } + id = w; + readWindowSize(); +} + +bool WindowPreview::previewsAvailable() const +{ + if (!KWindowSystem::compositingActive()) { + return false; + } +#ifdef Q_WS_X11 + // hackish way to find out if KWin has the effect enabled, + // TODO provide proper support + Display* dpy = QX11Info::display(); + Atom atom = XInternAtom(dpy, "_KDE_WINDOW_PREVIEW", False); + int cnt; + Atom* list = XListProperties(dpy, DefaultRootWindow( dpy ), &cnt); + if (list != NULL) { + bool ret = ( qFind(list, list + cnt, atom) != list + cnt ); + XFree(list); + return ret; + } +#endif + return false; +} + +QSize WindowPreview::sizeHint() const +{ + if (id == 0) { + return QSize(); + } + if (!windowSize.isValid()) { + readWindowSize(); + } + QSize s = windowSize; + s.scale(200, 150, Qt::KeepAspectRatio); + return s; +} + +void WindowPreview::readWindowSize() const +{ +#ifdef Q_WS_X11 + Window r; + int x, y; + unsigned int w, h, b, d; + if (XGetGeometry(QX11Info::display(), id, &r, &x, &y, &w, &h, &b, &d)) { + windowSize = QSize( w, h ); + } else { + windowSize = QSize(); + } + kDebug()<winId(), atom); + return; + } + if (!windowSize.isValid()) { + readWindowSize(); + } + if (!windowSize.isValid()) { + XDeleteProperty(dpy, parentWidget()->winId(), atom); + return; + } + Q_ASSERT( parentWidget()->isWindow()); // parent must be toplevel + long data[] = { 1, 5, id, x(), y(), width(), height() }; + XChangeProperty(dpy, parentWidget()->winId(), atom, atom, 32, PropModeReplace, + reinterpret_cast< unsigned char* >( data ), sizeof( data ) / sizeof( data[ 0 ] )); +#endif +} + +} +#include "tooltip_p.moc" diff --git a/tooltip_p.h b/tooltip_p.h new file mode 100644 index 000000000..fc62c15f7 --- /dev/null +++ b/tooltip_p.h @@ -0,0 +1,80 @@ +/* + * Copyright 2007 by Dan Meltzer + * Copyright (C) 2008 by Alexis Ménard + * + * This program 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, or + * (at your option) any later version. + + * + * 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PLASMA_TOOLTIP_P_H +#define PLASMA_TOOLTIP_P_H + +#include // base class +#include // stack allocated +#include // stack allocated +#include // stack allocated +#include + +#include //ToolTipData struct + +namespace Plasma { + + class ToolTipPrivate; + +/** + @author Dan Meltzer +* A Singleton tooltip. Before calling show it is necessary to +* call setLocation and setData +*/ +class ToolTip : public QWidget +{ + Q_OBJECT +public: + ToolTip(); + ~ToolTip(); + void updateTheme(); + void setData(const ToolTipManager::ToolTipData &data); + void prepareShowing(); + +protected: + void showEvent(QShowEvent *); + void mouseReleaseEvent(QMouseEvent *); + + void resizeEvent(QResizeEvent *); + void paintEvent(QPaintEvent *); + +private: + ToolTipPrivate *const d; +}; + +class WindowPreview : public QWidget +{ + Q_OBJECT +public: + void setWindowId(WId w); + void setInfo(); + virtual QSize sizeHint() const; + bool previewsAvailable() const; +private: + void readWindowSize() const; + WId id; + mutable QSize windowSize; +}; + +} + +#endif + diff --git a/tooltipmanager.cpp b/tooltipmanager.cpp new file mode 100644 index 000000000..b9009da10 --- /dev/null +++ b/tooltipmanager.cpp @@ -0,0 +1,294 @@ +/************************************************************************** +* Copyright 2007 by Dan Meltzer * +* Copyright (C) 2008 by Alexis Ménard * +* * +* 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) any later version. * +* * +* 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, write to the * +* Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * +***************************************************************************/ +#include "tooltipmanager.h" + +//Qt +#include +#include +#include +#include +#include + +//KDE +#include + +//Plasma +#include +#include +#include +#include +#include +#include +namespace Plasma +{ +class ToolTipManagerPrivate +{ +public : + ToolTipManagerPrivate() + : showTimer(0) + , hideTimer(0) + , currentWidget(0) + { + + } + ~ToolTipManagerPrivate() + { + + } + + void showToolTip(); + void resetShownState(); + + QGraphicsWidget *currentWidget; + bool isShown; + bool delayedHide; + QTimer *showTimer; + QTimer *hideTimer; + QMap tooltips; +}; + +//TOOLTIP IMPLEMENTATION +class ToolTipManagerSingleton +{ + public: + ToolTipManagerSingleton() + { + } + ToolTipManager self; +}; +K_GLOBAL_STATIC( ToolTipManagerSingleton, privateInstance ) + +ToolTipManager *ToolTipManager::self() +{ + return &privateInstance->self; +} + +ToolTipManager::ToolTipManager(QObject* parent) + : QObject(parent), + d(new ToolTipManagerPrivate) +{ + connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeUpdated())); + themeUpdated(); + + d->showTimer = new QTimer(this); + d->showTimer->setSingleShot(true); + d->hideTimer = new QTimer(this); + d->hideTimer->setSingleShot(true); + + connect(d->showTimer, SIGNAL(timeout()), SLOT(showToolTip())); + connect(d->hideTimer, SIGNAL(timeout()), SLOT(resetShownState())); +} + +ToolTipManager::~ToolTipManager() +{ + if (d) delete d; +} + +void ToolTipManager::showToolTip(QGraphicsWidget *widget) +{ + ToolTip * tooltip = d->tooltips.value(widget); + if (tooltip) { + if (d->currentWidget) { + hideToolTip(d->currentWidget); + } + d->hideTimer->stop(); + d->delayedHide = false; + d->showTimer->stop(); + d->currentWidget = widget; + if (d->isShown) { + // small delay to prevent unnecessary showing when the mouse is moving quickly across items + // which can be too much for less powerful CPUs to keep up with + d->showTimer->start(200); + } else { + d->showTimer->start(500); + } + } +} + +bool ToolTipManager::isVisible(QGraphicsWidget *widget) +{ + ToolTip * tooltip = d->tooltips.value(widget); + if (tooltip) { + return tooltip->isVisible(); + } + else { + return false; + } +} + +void ToolTipManager::delayedHideToolTip() +{ + d->showTimer->stop(); // stop the timer to show the tooltip + d->delayedHide = true; + d->hideTimer->start(250); +} + +void ToolTipManager::hideToolTip(QGraphicsWidget *widget) +{ + ToolTip * tooltip = d->tooltips.value(widget); + if (tooltip) { + d->showTimer->stop(); // stop the timer to show the tooltip + d->delayedHide = false; + d->currentWidget = 0; + tooltip->hide(); + } +} + +void ToolTipManager::registerToolTipData(QGraphicsWidget *widget,const ToolTipManager::ToolTipData &data) +{ + if (!d->tooltips.contains(widget)) { + //the tooltip is not registered we add it in our map of tooltips + d->tooltips.insert(widget,new ToolTip()); + ToolTip * tooltip = d->tooltips.value(widget); + tooltip->setData(data); + tooltip->updateTheme(); + } +} + +void ToolTipManager::unregisterToolTip(QGraphicsWidget *widget) +{ + if (d->tooltips.contains(widget)) { + ToolTip * tooltip = d->tooltips.take(widget); + if (tooltip) { + delete tooltip; + } + } +} + +void ToolTipManager::updateToolTipData(QGraphicsWidget *widget,const ToolTipData &data) +{ + if (!d->tooltips.contains(widget)) { + return; + } + else { + ToolTip * tooltip = d->tooltips.value(widget); + tooltip->setData(data); + } +} + +bool ToolTipManager::hasToolTip(QGraphicsWidget *widget) +{ + return d->tooltips.contains(widget); +} + +void ToolTipManager::themeUpdated() +{ + QMapIterator iterator(d->tooltips); + while (iterator.hasNext()) { + iterator.next(); + iterator.value()->updateTheme(); + } +} + +void ToolTipManagerPrivate::resetShownState() +{ + if (currentWidget) { + ToolTip * tooltip = tooltips.value(currentWidget); + if ((tooltip && !tooltip->isVisible()) || delayedHide) { + //One might have moused out and back in again + delayedHide = false; + isShown = false; + tooltip->hide(); + currentWidget = 0; + } + } +} + +void ToolTipManagerPrivate::showToolTip() +{ + if (!currentWidget) { + return; + } + + ToolTip * tooltip = tooltips.value(currentWidget); + if (tooltip) { + tooltip->prepareShowing(); + tooltip->move(ToolTipManager::popupPosition(currentWidget,tooltip->size())); + isShown = true; //ToolTip is visible + } +} + +QPoint ToolTipManager::popupPosition(const QGraphicsItem * item, const QSize &s) +{ + const Plasma::Applet * applet = dynamic_cast(item); + if (!applet) { + const QGraphicsItem * currentItem = item->parentItem(); + while(currentItem->parentItem() && !dynamic_cast(currentItem)) + { + currentItem=currentItem->parentItem(); + } + applet = dynamic_cast(currentItem); + if (!applet) return QPoint(0,0); + } + QGraphicsView *v = applet->view(); + Q_ASSERT(v); + + QPoint pos = v->mapFromScene(item->scenePos()); + pos = v->mapToGlobal(pos); + //kDebug() << "==> position is" << scenePos() << v->mapFromScene(scenePos()) << pos; + Plasma::View *pv = dynamic_cast(v); + + Plasma::Location loc = Floating; + if (pv) { + loc = pv->containment()->location(); + } + + switch (loc) { + case BottomEdge: + pos = QPoint(pos.x(), pos.y() - s.height()); + break; + case TopEdge: + pos = QPoint(pos.x(), pos.y() + (int)item->boundingRect().size().height()); + break; + case LeftEdge: + pos = QPoint(pos.x() + (int)item->boundingRect().size().width(), pos.y()); + break; + case RightEdge: + pos = QPoint(pos.x() - s.width(), pos.y()); + break; + default: + if (pos.y() - s.height() > 0) { + pos = QPoint(pos.x(), pos.y() - s.height()); + } else { + pos = QPoint(pos.x(), pos.y() + (int)item->boundingRect().size().height()); + } + } + + //are we out of screen? + + QRect screenRect = QApplication::desktop()->screenGeometry(pv ? pv->containment()->screen() : -1); + //kDebug() << "==> rect for" << (pv ? pv->containment()->screen() : -1) << "is" << screenRect; + + if (pos.rx() + s.width() > screenRect.right()) { + pos.rx() -= ((pos.rx() + s.width()) - screenRect.right()); + } + + if (pos.ry() + s.height() > screenRect.bottom()) { + pos.ry() -= ((pos.ry() + s.height()) - screenRect.bottom()); + } + pos.rx() = qMax(0, pos.rx()); + + return pos; +} + +//PRIVATE CLASS IMPLEMENTATION +} + +#include "tooltipmanager.moc" \ No newline at end of file diff --git a/tooltipmanager.h b/tooltipmanager.h new file mode 100644 index 000000000..a6e6d4469 --- /dev/null +++ b/tooltipmanager.h @@ -0,0 +1,89 @@ +/*************************************************************************** +* Copyright (C) 2007 by Alexis Ménard * +* Copyright 2007 by Dan Meltzer * +* * +* 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) any later version. * +* * +* 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, write to the * +* Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * +***************************************************************************/ + +#ifndef PLASMA_TOOLTIP_MANAGER_H +#define PLASMA_TOOLTIP_MANAGER_H + +//plasma +#include +#include + +//KDE +#include + +#ifdef Q_WS_X11 +#include +#endif + +//X11 +#ifdef Q_WS_X11 +#include +#include +#endif + +namespace Plasma +{ + class ToolTipManagerPrivate; + + /** + * @short The class to manage a tooltip on a plasma applet + * + * Manage tooltip on plasma applet, before using it you have to set information + */ + class PLASMA_EXPORT ToolTipManager : public QObject + { + Q_OBJECT + public: + struct ToolTipData + { + ToolTipData() : windowToPreview( 0 ) {} + QString mainText; //Important information + QString subText; //Elaborates on the Main Text + QPixmap image; // Icon to show; + WId windowToPreview; // Id of window to show preview + }; + + static ToolTipManager *self(); + /** + * Default constructor. Usually you want to use the singleton instead. + */ + explicit ToolTipManager(QObject* parent = 0); + ~ToolTipManager(); + void showToolTip(QGraphicsWidget *widget); + bool isVisible(QGraphicsWidget *widget); + void delayedHideToolTip(); + void hideToolTip(QGraphicsWidget *widget); + void registerToolTipData(QGraphicsWidget *widget,const ToolTipData &data); + void unregisterToolTip(QGraphicsWidget *widget); + void updateToolTipData(QGraphicsWidget *widget,const ToolTipData &data); + bool hasToolTip(QGraphicsWidget *widget); + static QPoint popupPosition(const QGraphicsItem * item, const QSize &s); + + private Q_SLOTS: + void themeUpdated(); + + private: + friend class ToolTipManagerSingleton; + ToolTipManagerPrivate* const d; + Q_PRIVATE_SLOT(d, void showToolTip()) + Q_PRIVATE_SLOT(d, void resetShownState()) + }; +} +#endif