add more spacing
[personal-kdebase.git] / workspace / krunner / interfaces / default / resultitem.cpp
blob11e1107cfa3e141a966ca9d8fbeb3a3de74da826
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> *
5 * *
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. *
10 * *
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. *
15 * *
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"
25 #include <math.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>
39 #include <KDebug>
40 #include <KIcon>
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
55 public:
56 Private(ResultItem *item, Plasma::FrameSvg *f)
57 : q(item),
58 match(0),
59 frame(f),
60 tempTransp(1.0),
61 highlight(false),
62 index(-1),
63 rowStride(6),
64 highlightTimerId(0),
65 animation(0),
66 needsMoving(false)
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;
76 ~Private()
78 delete animation;
81 static ResultItemSignaller * signaller()
83 if (!s_signaller) {
84 s_signaller = new ResultItemSignaller;
87 return s_signaller;
90 QPointF pos();
91 void appear();
92 void move();
93 void init();
94 //void animationComplete();
96 static ResultItemSignaller *s_signaller;
97 static int s_removingCount;
98 static int s_fontHeight;
100 ResultItem * q;
101 Plasma::QueryMatch match;
102 Plasma::FrameSvg *frame;
103 // static description
104 QIcon icon;
105 // dyn params
106 QBrush bgBrush;
107 QPixmap fadeout;
108 qreal tempTransp;
109 int highlight;
110 int index;
111 int rowStride;
112 int highlightTimerId;
113 QGraphicsItemAnimation *animation;
114 bool isFavorite : 1;
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()
132 if (animation) {
133 q->animationComplete();
136 //TODO: maybe have them scatter in versus expand/spin in place into view?
137 QPointF p(pos());
138 qreal halfway = ResultItem::BOUNDING_WIDTH * 0.5;
139 qreal mostway = ResultItem::BOUNDING_WIDTH * 0.1;
141 q->setPos(p);
142 q->scale(0.0, 0.0);
143 q->setPos(p + QPointF(halfway, halfway));
144 q->becomeVisible();
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);
154 timer->start();
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;
162 if (animation) {
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()));
177 timer->start();
180 //void ResultItem::Private::animationComplete()
181 void ResultItem::animationComplete()
183 delete d->animation;
184 d->animation = 0;
185 resetTransform();
188 ResultItem::ResultItem(const Plasma::QueryMatch &match, QGraphicsWidget *parent, Plasma::FrameSvg *frame)
189 : QGraphicsWidget(parent),
190 d(new Private(this, frame))
192 setMatch(match);
193 d->init();
194 connect(Private::signaller(), SIGNAL(animate()), this, SLOT(animate()));
195 setZValue(0);
198 void ResultItem::Private::init()
200 //QTimer * timer = new QTimer(q);
201 //connect(timer, SIGNAL(timeout()), q, SLOT(slotTestTransp()));
202 //timer->start(50);
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();
219 delete d;
222 void ResultItem::setMatch(const Plasma::QueryMatch &match)
224 d->match = match;
225 d->icon = KIcon(match.icon());
227 int hue = 0;
228 switch (match.type()) {
229 case Plasma::QueryMatch::CompletionMatch:
230 hue = 10; // reddish
231 break;
232 case Plasma::QueryMatch::InformationalMatch:
233 case Plasma::QueryMatch::HelperMatch:
234 hue = 110; // green
235 break;
236 case Plasma::QueryMatch::ExactMatch:
237 hue = 60; // gold
238 break;
239 case Plasma::QueryMatch::PossibleMatch:
240 default:
241 hue = 40; // browny
242 break;
245 QColor mix = QColor::fromHsv(hue, 160, 150);
246 mix.setAlpha(180);
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);
253 d->bgBrush = gr;*/
254 d->bgBrush = mix;
255 update();
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
290 return d->icon;
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) {
312 return;
315 bool first = d->index == -1;
316 d->index = index;
317 d->needsMoving = false;
319 if (index < 0) {
320 index = -1;
321 return;
324 //kDebug() << index << first << hasFocus();
325 if (first) {
326 d->appear();
327 } else if (d->s_removingCount) {
328 d->needsMoving = true;
329 } else {
330 d->move();
334 void ResultItem::animate()
336 //kDebug() << "can animate now" << d->needsMoving;
337 if (d->needsMoving) {
338 d->needsMoving = false;
339 d->move();
343 void ResultItem::becomeVisible()
345 show();
348 int ResultItem::index() const
350 return d->index;
353 void ResultItem::setRowStride(int stride)
355 if (d->rowStride == stride) {
356 return;
359 d->rowStride = stride;
360 if (d->index != -1) {
361 d->move();
365 int ResultItem::rowStride() const
367 return d->rowStride;
370 void ResultItem::remove()
372 delete d->animation;
373 d->animation = 0;
375 QPointF p(d->pos());
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()));
390 timer->start();
393 void ResultItem::run(Plasma::RunnerManager *manager)
395 manager->run(d->match);
398 void ResultItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
400 Q_UNUSED(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);
411 painter->save();
413 // Draw background
414 if (hasFocus()) {
415 d->frame->setElementPrefix("hover");
416 } else {
417 d->frame->setElementPrefix("normal");
419 d->frame->resizeFrame(rect.size());
420 d->frame->paintFrame(painter, rect.topLeft());
422 painter->restore();
423 painter->save();
426 The following implements blinking Blinking
427 if (hasFocus() || d->tempTransp >= 1.5) {
428 painter->setOpacity(1.0);
429 } else {
430 qreal transp = d->tempTransp - int(d->tempTransp);
431 //qDebug() << "transparency of" << transp << "from" << d->tempTransp;
432 if (transp > 0.5) {
433 painter->setOpacity(2.0 - (2.0 * transp));
434 } else {
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()) {
455 #ifdef NO_GROW_ANIM
456 if (d->highlight > 2) {
457 #else
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) {
462 #endif
463 painter->drawPixmap(iconPadding, iconPadding, d->icon.pixmap(iconSize, QIcon::Active));
464 } else {
465 drawMixed = true;
467 #ifdef NO_GROW_ANIM
468 ++d->highlight;
469 #else
470 if (!d->animation) {
471 ++d->highlight;
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));
478 #endif
480 if (!d->highlightTimerId) {
481 d->highlightTimerId = startTimer(40);
484 } else if (d->highlight > 0) {
485 drawMixed = true;
487 #ifdef NO_GROW_ANIM
488 --d->highlight;
489 #else
490 if (!d->animation) {
491 --d->highlight;
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));
498 #endif
500 if (!d->highlightTimerId) {
501 d->highlightTimerId = startTimer(40);
503 } else {
504 painter->drawPixmap(iconPadding, iconPadding, d->icon.pixmap(iconSize, QIcon::Disabled));
507 if (drawMixed) {
508 qreal factor = .2;
510 if (d->highlight == 1) {
511 factor = .4;
512 } else if (d->highlight == 2) {
513 factor = .6;
514 } else if (d->highlight > 2) {
515 factor = .8;
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));
525 painter->restore();
527 // Draw hover/selection rects
529 if (isSelected()) {
530 //TODO: fancier, please
531 painter->save();
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);
536 if (mouseOver) {
537 color = color.lighter();
539 //QPen pen(palette().color(QPalette::Highlight), 2);
540 QPen pen(color, 2);
541 painter->strokePath(Plasma::PaintUtils::roundedRectangle(rect, 6), pen);
542 painter->restore();
543 } else if (mouseOver) {
544 painter->save();
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);
548 painter->restore();
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);
560 QPainter p(&pixmap);
561 p.setPen(textColor);
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);
580 p.end();
582 // Draw a drop shadow if we have a bright text color
583 if (qGray(textColor.rgb()) > 192) {
584 const int blur = 2;
585 const QPoint offset(1, 1);
587 QImage shadow(pixmap.size() + QSize(blur * 2, blur * 2), QImage::Format_ARGB32_Premultiplied);
588 p.begin(&shadow);
589 p.setCompositionMode(QPainter::CompositionMode_Source);
590 p.fillRect(shadow.rect(), Qt::transparent);
591 p.drawPixmap(blur, blur, pixmap);
592 p.end();
594 Plasma::PaintUtils::shadowBlur(shadow, blur, Qt::black);
596 // Draw the shadow
597 painter->drawImage(textRect.topLeft() - QPoint(blur, blur) + offset, shadow);
600 // Draw the text
601 painter->drawPixmap(textRect.topLeft(), pixmap);
602 painter->setClipping(oldClipping);
605 void ResultItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *e)
607 QGraphicsItem::hoverLeaveEvent(e);
608 //update();
609 //emit hoverLeave(this);
610 setFocus(Qt::MouseFocusReason);
613 void ResultItem::hoverEnterEvent(QGraphicsSceneHoverEvent *e)
615 QGraphicsItem::hoverEnterEvent(e);
616 // update();
617 // emit hoverEnter(this);
618 //setFocusItem(this);
619 setFocus(Qt::MouseFocusReason);
622 void ResultItem::timerEvent(QTimerEvent *e)
624 Q_UNUSED(e)
626 d->tempTransp += 0.1;
627 killTimer(d->highlightTimerId);
628 d->highlightTimerId = 0;
630 update();
633 void ResultItem::slotTestTransp()
635 d->tempTransp += 0.02;
636 if (d->tempTransp >= 1.0)
637 d->tempTransp -= 1.0;
638 update();
641 void ResultItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *)
643 emit activated(this);
646 void ResultItem::focusInEvent(QFocusEvent * event)
648 QGraphicsWidget::focusInEvent(event);
649 setZValue(1);
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);
660 setZValue(0);
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);
672 } else {
673 QGraphicsWidget::keyPressEvent(event);
677 QVariant ResultItem::itemChange(GraphicsItemChange change, const QVariant &value)
679 return QGraphicsWidget::itemChange(change, value);
682 #include "resultitem.moc"