1 /***************************************************************************
2 * Copyright 2007 by Enrico Ros <enrico.ros@gmail.com> *
3 * Copyright 2007 by Riccardo Iaconelli <ruphy@kde.org> *
4 * Copyright 2008 by Davide Bettio <davide.bettio@kdemail.net> *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
20 ***************************************************************************/
23 #include "resultitem.h"
27 #include <QtCore/QTimeLine>
28 #include <QtCore/QDebug>
29 #include <QtCore/QtGlobal>
30 #include <QtCore/QTimer>
31 #include <QtGui/QApplication>
32 #include <QtGui/QPainter>
33 #include <QtGui/QStyleOptionGraphicsItem>
34 #include <QtGui/QGraphicsItemAnimation>
35 #include <QtGui/QGraphicsLinearLayout>
36 #include <QtGui/QGraphicsScene>
37 #include <QtGui/QGraphicsView>
42 #include <Plasma/PaintUtils>
43 #include <Plasma/Plasma>
44 #include <Plasma/RunnerManager>
45 #include <Plasma/PaintUtils>
46 #include <Plasma/FrameSvg>
48 #define TEXT_AREA_HEIGHT ResultItem::MARGIN + ResultItem::TEXT_MARGIN*2 + ResultItem::Private::s_fontHeight
49 //#define NO_GROW_ANIM
51 void shadowBlur(QImage
&image
, int radius
, const QColor
&color
);
53 class ResultItem::Private
56 Private(ResultItem
*item
, Plasma::FrameSvg
*f
)
68 if (s_fontHeight
< 1) {
69 //FIXME: reset when the application font changes
70 QFontMetrics
fm(q
->font());
71 s_fontHeight
= fm
.height();
72 //kDebug() << "font height is: " << s_fontHeight;
81 static ResultItemSignaller
* signaller()
84 s_signaller
= new ResultItemSignaller
;
94 //void animationComplete();
96 static ResultItemSignaller
*s_signaller
;
97 static int s_removingCount
;
98 static int s_fontHeight
;
101 Plasma::QueryMatch match
;
102 Plasma::FrameSvg
*frame
;
103 // static description
112 int highlightTimerId
;
113 QGraphicsItemAnimation
*animation
;
115 bool needsMoving
: 1;
118 int ResultItem::Private::s_removingCount
= 0;
119 ResultItemSignaller
* ResultItem::Private::s_signaller
= 0;
120 int ResultItem::Private::s_fontHeight
= 0;
122 QPointF
ResultItem::Private::pos()
124 const int x
= (index
% rowStride
) * ResultItem::BOUNDING_WIDTH
+ 4;
125 const int y
= (index
/ rowStride
) * (ResultItem::BOUNDING_HEIGHT
+ s_fontHeight
) + 4;
126 //kDebug() << x << y << "for" << index;
127 return QPointF(x
, y
);
130 void ResultItem::Private::appear()
133 q
->animationComplete();
136 //TODO: maybe have them scatter in versus expand/spin in place into view?
138 qreal halfway
= ResultItem::BOUNDING_WIDTH
* 0.5;
139 qreal mostway
= ResultItem::BOUNDING_WIDTH
* 0.1;
143 q
->setPos(p
+ QPointF(halfway
, halfway
));
145 animation
= new QGraphicsItemAnimation();
146 animation
->setItem(q
);
147 animation
->setScaleAt(0.5, 0.1, 1.0);
148 animation
->setScaleAt(1.0, 1.0, 1.0);
149 animation
->setPosAt(0.5, p
+ QPointF(mostway
, 0));
150 animation
->setPosAt(1.0, p
);
151 QTimeLine
* timer
= new QTimeLine(100, animation
);
152 animation
->setTimeLine(timer
);
155 // QTimer::singleShot(50, q, SLOT(becomeVisible()));
156 connect(timer
, SIGNAL(finished()), q
, SLOT(animationComplete()));
159 void ResultItem::Private::move()
161 //qDebug() << "moving to" << index << rowStride;
163 q
->animationComplete();
166 QGraphicsLayoutItem
*parent
= q
->parentLayoutItem();
167 QRect contentsRect
= parent
? parent
->contentsRect().toRect() : q
->scene()->sceneRect().toRect();
169 QGraphicsItemAnimation
* animation
= new QGraphicsItemAnimation(q
);
170 animation
->setItem(q
);
171 QTimeLine
*timer
= new QTimeLine(150, animation
);
172 timer
->setCurveShape(QTimeLine::EaseOutCurve
);
173 animation
->setTimeLine(timer
);
175 animation
->setPosAt(1.0, pos());
176 QObject::connect(timer
, SIGNAL(finished()), q
, SLOT(animationComplete()));
180 //void ResultItem::Private::animationComplete()
181 void ResultItem::animationComplete()
188 ResultItem::ResultItem(const Plasma::QueryMatch
&match
, QGraphicsWidget
*parent
, Plasma::FrameSvg
*frame
)
189 : QGraphicsWidget(parent
),
190 d(new Private(this, frame
))
194 connect(Private::signaller(), SIGNAL(animate()), this, SLOT(animate()));
198 void ResultItem::Private::init()
200 //QTimer * timer = new QTimer(q);
201 //connect(timer, SIGNAL(timeout()), q, SLOT(slotTestTransp()));
204 q
->setFlag(QGraphicsItem::ItemIsFocusable
);
205 q
->setFlag(QGraphicsItem::ItemIsSelectable
);
206 q
->setAcceptHoverEvents(true);
207 q
->setFocusPolicy(Qt::TabFocus
);
208 q
->resize(ITEM_SIZE
, ITEM_SIZE
+ TEXT_AREA_HEIGHT
);
211 ResultItem::~ResultItem()
213 --Private::s_removingCount
;
215 if (Private::s_removingCount
< 1) {
216 Private::s_removingCount
= 0;
217 Private::signaller()->startAnimations();
222 void ResultItem::setMatch(const Plasma::QueryMatch
&match
)
225 d
->icon
= KIcon(match
.icon());
228 switch (match
.type()) {
229 case Plasma::QueryMatch::CompletionMatch
:
232 case Plasma::QueryMatch::InformationalMatch
:
233 case Plasma::QueryMatch::HelperMatch
:
236 case Plasma::QueryMatch::ExactMatch
:
239 case Plasma::QueryMatch::PossibleMatch
:
245 QColor mix
= QColor::fromHsv(hue
, 160, 150);
247 /* QColor grey(61, 61, 61, 200);
248 QRectF rect = boundingRect();
249 QLinearGradient gr(QPointF(0, 0), geometry().bottomRight());
250 gr.setColorAt(0.0, mix);
251 gr.setColorAt(0.8, grey);
252 gr.setColorAt(1.0, mix);
258 QString
ResultItem::id() const
260 return d
->match
.id();
263 bool ResultItem::compare(const ResultItem
*one
, const ResultItem
*other
)
265 return other
->d
->match
< one
->d
->match
;
268 bool ResultItem::operator<(const ResultItem
&other
) const
270 return d
->match
< other
.d
->match
;
273 QString
ResultItem::name() const
275 return d
->match
.text();
278 QString
ResultItem::description() const
280 return d
->match
.subtext();
283 QString
ResultItem::data() const
285 return d
->match
.data().toString();
288 QIcon
ResultItem::icon() const
293 Plasma::QueryMatch::Type
ResultItem::group() const
295 return d
->match
.type();
298 qreal
ResultItem::priority() const
300 // TODO, need to fator in more things than just this
301 return d
->match
.relevance();
304 bool ResultItem::isFavorite() const
306 return d
->isFavorite
;
309 void ResultItem::setIndex(int index
)
311 if (d
->index
== index
) {
315 bool first
= d
->index
== -1;
317 d
->needsMoving
= false;
324 //kDebug() << index << first << hasFocus();
327 } else if (d
->s_removingCount
) {
328 d
->needsMoving
= true;
334 void ResultItem::animate()
336 //kDebug() << "can animate now" << d->needsMoving;
337 if (d
->needsMoving
) {
338 d
->needsMoving
= false;
343 void ResultItem::becomeVisible()
348 int ResultItem::index() const
353 void ResultItem::setRowStride(int stride
)
355 if (d
->rowStride
== stride
) {
359 d
->rowStride
= stride
;
360 if (d
->index
!= -1) {
365 int ResultItem::rowStride() const
370 void ResultItem::remove()
376 d
->needsMoving
= false;
377 d
->animation
= new QGraphicsItemAnimation();
378 d
->animation
->setItem(this);
379 d
->animation
->setScaleAt(0.0, 1.0, 1.0);
380 d
->animation
->setScaleAt(0.5, 0.1, 1.0);
381 d
->animation
->setScaleAt(1.0, 0.0, 0.0);
382 d
->animation
->setPosAt(0.0, p
+ QPointF(0.0, 0.0));
383 d
->animation
->setPosAt(0.5, p
+ QPointF(32.0*0.9, 0.0));
384 d
->animation
->setPosAt(1.0, p
+ QPointF(32.0, 32.0));
385 QTimeLine
* timer
= new QTimeLine(150, d
->animation
);
386 d
->animation
->setTimeLine(timer
);
387 ++Private::s_removingCount
;
389 connect(timer
, SIGNAL(finished()), this, SLOT(deleteLater()));
393 void ResultItem::run(Plasma::RunnerManager
*manager
)
395 manager
->run(d
->match
);
398 void ResultItem::paint(QPainter
*painter
, const QStyleOptionGraphicsItem
*option
, QWidget
*widget
)
402 bool oldClipping
= painter
->hasClipping();
403 painter
->setClipping(false);
405 QRectF rect
= boundingRect().adjusted(0, 0, 0, -(TEXT_AREA_HEIGHT
));
406 QRect iRect
= rect
.toRect().adjusted(PADDING
, PADDING
, -PADDING
, -PADDING
);
407 QSize iconSize
= iRect
.size().boundedTo(QSize(64, 64));
408 int iconPadding
= qMax((int(rect
.width()) - iconSize
.width()) / 2, 0);
409 //kDebug() << iconPadding << PADDING;
410 painter
->setRenderHint(QPainter::Antialiasing
);
415 d
->frame
->setElementPrefix("hover");
417 d
->frame
->setElementPrefix("normal");
419 d
->frame
->resizeFrame(rect
.size());
420 d
->frame
->paintFrame(painter
, rect
.topLeft());
426 The following implements blinking Blinking
427 if (hasFocus() || d->tempTransp >= 1.5) {
428 painter->setOpacity(1.0);
430 qreal transp = d->tempTransp - int(d->tempTransp);
431 //qDebug() << "transparency of" << transp << "from" << d->tempTransp;
433 painter->setOpacity(2.0 - (2.0 * transp));
435 painter->setOpacity(2.0 * transp);
438 if (!d->highlightTimerId) {
439 d->highlightTimerId = startTimer(40);
444 if (!hasFocus() && d
->tempTransp
< 0.9) {
445 painter
->setOpacity(d
->tempTransp
);
447 if (!d
->highlightTimerId
) {
448 d
->highlightTimerId
= startTimer(40);
452 bool drawMixed
= false;
454 if (hasFocus() || isSelected()) {
456 if (d
->highlight
> 2) {
458 // here's what the next line means:
459 // we check to see if the scene has focus, but that's overridden by the mouse hovering an
460 // item ... or unless we are over 2 ticks into the higlight anim. complex but it works
461 if (((scene() && !scene()->views().isEmpty() && !scene()->views()[0]->hasFocus()) && !(option
->state
& QStyle::State_MouseOver
)) || d
->highlight
> 2) {
463 painter
->drawPixmap(iconPadding
, iconPadding
, d
->icon
.pixmap(iconSize
, QIcon::Active
));
472 if (d
->highlight
== 1) {
473 setGeometry(sceneBoundingRect().adjusted(-1, -1, 1, 1));
474 } else if (d
->highlight
== 3) {
475 setGeometry(sceneBoundingRect().adjusted(-2, -2, 2, 2));
480 if (!d
->highlightTimerId
) {
481 d
->highlightTimerId
= startTimer(40);
484 } else if (d
->highlight
> 0) {
492 if (d
->highlight
== 0) {
493 setGeometry(sceneBoundingRect().adjusted(1, 1, -1, -1));
494 } else if (d
->highlight
== 2) {
495 setGeometry(sceneBoundingRect().adjusted(2, 2, -2, -2));
500 if (!d
->highlightTimerId
) {
501 d
->highlightTimerId
= startTimer(40);
504 painter
->drawPixmap(iconPadding
, iconPadding
, d
->icon
.pixmap(iconSize
, QIcon::Disabled
));
510 if (d
->highlight
== 1) {
512 } else if (d
->highlight
== 2) {
514 } else if (d
->highlight
> 2) {
518 qreal activeOpacity
= painter
->opacity() * factor
;
520 painter
->setOpacity(painter
->opacity() * (1 - factor
));
521 painter
->drawPixmap(iconPadding
, iconPadding
, d
->icon
.pixmap(iconSize
, QIcon::Disabled
));
522 painter
->setOpacity(activeOpacity
);
523 painter
->drawPixmap(iconPadding
, iconPadding
, d
->icon
.pixmap(iconSize
, QIcon::Active
));
527 // Draw hover/selection rects
530 //TODO: fancier, please
532 painter->translate(0.5, 0.5);
533 painter->setBrush(Qt::transparent);
534 painter->setPen(QPen(Qt::white, 2));
535 QColor color = palette().color(QPalette::Highlight);
537 color = color.lighter();
539 //QPen pen(palette().color(QPalette::Highlight), 2);
541 painter->strokePath(Plasma::PaintUtils::roundedRectangle(rect, 6), pen);
543 } else if (mouseOver) {
545 painter->translate(0.5, 0.5);
546 QPen pen(palette().color(QPalette::Highlight).lighter(), 1);
547 painter->strokePath(Plasma::PaintUtils::roundedRectangle(rect, 6), pen);
552 QRect
textRect(iRect
.bottomLeft() + QPoint(0, MARGIN
+ TEXT_MARGIN
), iRect
.bottomRight() + QPoint(0, TEXT_AREA_HEIGHT
));
554 // Draw the text on a pixmap
555 const QColor textColor
= Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor
);
556 const int width
= option
->fontMetrics
.width(name());
557 QPixmap
pixmap(textRect
.size());
558 pixmap
.fill(Qt::transparent
);
562 p
.drawText(pixmap
.rect(), width
> pixmap
.width() ? Qt::AlignLeft
: Qt::AlignCenter
, name());
564 // Fade the pixmap out at the end
565 if (width
> pixmap
.width()) {
566 if (d
->fadeout
.isNull() || d
->fadeout
.height() != pixmap
.height()) {
567 QLinearGradient
g(0, 0, 20, 0);
568 g
.setColorAt(0, layoutDirection() == Qt::LeftToRight
? Qt::white
: Qt::transparent
);
569 g
.setColorAt(1, layoutDirection() == Qt::LeftToRight
? Qt::transparent
: Qt::white
);
570 d
->fadeout
= QPixmap(20, textRect
.height());
571 d
->fadeout
.fill(Qt::transparent
);
572 QPainter
p(&d
->fadeout
);
573 p
.setCompositionMode(QPainter::CompositionMode_Source
);
574 p
.fillRect(d
->fadeout
.rect(), g
);
576 const QRect r
= QStyle::alignedRect(layoutDirection(), Qt::AlignRight
, d
->fadeout
.size(), pixmap
.rect());
577 p
.setCompositionMode(QPainter::CompositionMode_DestinationIn
);
578 p
.drawPixmap(r
.topLeft(), d
->fadeout
);
582 // Draw a drop shadow if we have a bright text color
583 if (qGray(textColor
.rgb()) > 192) {
585 const QPoint
offset(1, 1);
587 QImage
shadow(pixmap
.size() + QSize(blur
* 2, blur
* 2), QImage::Format_ARGB32_Premultiplied
);
589 p
.setCompositionMode(QPainter::CompositionMode_Source
);
590 p
.fillRect(shadow
.rect(), Qt::transparent
);
591 p
.drawPixmap(blur
, blur
, pixmap
);
594 Plasma::PaintUtils::shadowBlur(shadow
, blur
, Qt::black
);
597 painter
->drawImage(textRect
.topLeft() - QPoint(blur
, blur
) + offset
, shadow
);
601 painter
->drawPixmap(textRect
.topLeft(), pixmap
);
602 painter
->setClipping(oldClipping
);
605 void ResultItem::hoverLeaveEvent(QGraphicsSceneHoverEvent
*e
)
607 QGraphicsItem::hoverLeaveEvent(e
);
609 //emit hoverLeave(this);
610 setFocus(Qt::MouseFocusReason
);
613 void ResultItem::hoverEnterEvent(QGraphicsSceneHoverEvent
*e
)
615 QGraphicsItem::hoverEnterEvent(e
);
617 // emit hoverEnter(this);
618 //setFocusItem(this);
619 setFocus(Qt::MouseFocusReason
);
622 void ResultItem::timerEvent(QTimerEvent
*e
)
626 d
->tempTransp
+= 0.1;
627 killTimer(d
->highlightTimerId
);
628 d
->highlightTimerId
= 0;
633 void ResultItem::slotTestTransp()
635 d
->tempTransp
+= 0.02;
636 if (d
->tempTransp
>= 1.0)
637 d
->tempTransp
-= 1.0;
641 void ResultItem::mouseReleaseEvent(QGraphicsSceneMouseEvent
*)
643 emit
activated(this);
646 void ResultItem::focusInEvent(QFocusEvent
* event
)
648 QGraphicsWidget::focusInEvent(event
);
650 //setGeometry(sceneBoundingRect().adjusted(-4, -4, 4, 4));
651 if (!d
->highlightTimerId
) {
652 d
->highlightTimerId
= startTimer(40);
654 emit
hoverEnter(this);
657 void ResultItem::focusOutEvent(QFocusEvent
* event
)
659 QGraphicsWidget::focusOutEvent(event
);
661 //setGeometry(sceneBoundingRect().adjusted(4, 4, -4, -4));
662 if (!d
->highlightTimerId
) {
663 d
->highlightTimerId
= startTimer(40);
665 emit
hoverLeave(this);
668 void ResultItem::keyPressEvent(QKeyEvent
*event
)
670 if (event
->key() == Qt::Key_Enter
|| event
->key() == Qt::Key_Return
) {
671 emit
activated(this);
673 QGraphicsWidget::keyPressEvent(event
);
677 QVariant
ResultItem::itemChange(GraphicsItemChange change
, const QVariant
&value
)
679 return QGraphicsWidget::itemChange(change
, value
);
682 #include "resultitem.moc"