Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / libs / plasma / applethandle.cpp
blobba4daead1f68031ef03ff648f6f277e9e465af3c
1 /*
2 * Copyright 2007 by Kevin Ottens <ervin@kde.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "applethandle_p.h"
22 #include <QApplication>
23 #include <QtGui/QGraphicsSceneMouseEvent>
24 #include <QtGui/QLinearGradient>
25 #include <QtGui/QPainter>
26 #include <QtGui/QApplication>
28 #include <KColorScheme>
29 #include <KGlobalSettings>
30 #include <KIcon>
32 #include <cmath>
33 #include <math.h>
35 #include "applet.h"
36 #include "containment.h"
37 #include "corona.h"
38 #include "theme.h"
39 #include "view.h"
41 namespace Plasma
44 qreal _k_angleForPoints(const QPointF &center, const QPointF &pt1, const QPointF &pt2);
46 AppletHandle::AppletHandle(Containment *parent, Applet *applet)
47 : QObject(),
48 QGraphicsItem(parent),
49 m_pressedButton(NoButton),
50 m_containment(parent),
51 m_applet(applet),
52 m_opacity(0.0),
53 m_anim(FadeIn),
54 m_animId(0),
55 m_angle(0.0),
56 m_tempAngle(0.0),
57 m_scaleWidth(1.0),
58 m_scaleHeight(1.0),
59 m_buttonsOnRight(false),
60 m_pendingFade(false)
62 KColorScheme colors(QPalette::Active, KColorScheme::View, Theme::self()->colors());
63 m_gradientColor = colors.background(KColorScheme::NormalBackground).color();
65 QTransform originalMatrix = m_applet->transform();
66 QRectF rect(m_applet->boundingRect());
67 QPointF center = rect.center();
68 originalMatrix.translate(center.x(), center.y());
70 qreal cosine = originalMatrix.m11();
71 qreal sine = originalMatrix.m12();
73 m_angle = _k_angleForPoints(QPointF(0, 0),
74 QPointF(1, 0),
75 QPointF(cosine, sine));
77 m_applet->resetTransform();
79 calculateSize();
80 m_applet->setParentItem(this);
82 rect = QRectF(m_applet->pos(), m_applet->size());
83 center = rect.center();
84 QTransform matrix;
85 matrix.translate(center.x(), center.y());
86 matrix.rotateRadians(m_angle);
87 matrix.translate(-center.x(), -center.y());
88 setTransform(matrix);
89 m_hoverTimer = new QTimer(this);
90 m_hoverTimer->setSingleShot(true);
91 m_hoverTimer->setInterval(333);
93 connect(m_hoverTimer, SIGNAL(timeout()), this, SLOT(fadeIn()));
94 connect(m_applet, SIGNAL(destroyed(QObject*)), this, SLOT(appletDestroyed()));
96 setAcceptsHoverEvents(true);
97 m_hoverTimer->start();
99 setZValue(m_applet->zValue());
102 AppletHandle::~AppletHandle()
104 if (m_applet) {
105 m_applet->removeSceneEventFilter(this);
107 QRectF rect = QRectF(m_applet->pos(), m_applet->size());
108 QPointF center = m_applet->mapFromParent(rect.center());
110 QPointF newPos = transform().inverted().map(m_applet->pos());
111 m_applet->setPos(mapToParent(newPos));
113 QTransform matrix;
114 matrix.translate(center.x(), center.y());
115 matrix.rotateRadians(m_angle);
116 matrix.translate(-center.x(), -center.y());
117 m_applet->setTransform(matrix);
119 m_applet->setParentItem(m_containment);
121 m_applet->update(); // re-render the background, now we've transformed the applet
125 Applet *AppletHandle::applet() const
127 return m_applet;
130 QRectF Plasma::AppletHandle::boundingRect() const
132 return m_rect;
135 void AppletHandle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
137 Q_UNUSED(option);
138 Q_UNUSED(widget);
140 painter->save();
141 painter->setOpacity(m_opacity);
143 painter->save();
144 painter->setOpacity(m_opacity * 0.4);
145 painter->setPen(Qt::NoPen);
146 painter->setRenderHints(QPainter::Antialiasing);
147 QLinearGradient gr(boundingRect().topLeft(), boundingRect().bottomRight());
148 gr.setColorAt(0, m_gradientColor);
149 gr.setColorAt(0.1, KColorScheme::shade(m_gradientColor, KColorScheme::LightShade));
150 gr.setColorAt(1, KColorScheme::shade(m_gradientColor, KColorScheme::DarkShade));
151 painter->setBrush(gr);
152 QPainterPath path = Plasma::roundedRectangle(boundingRect(), 10);
154 if (m_applet) {
155 QPainterPath shape = m_applet->shape();
157 if (!shape.isEmpty()) {
158 path = path.subtracted(m_applet->mapToParent(m_applet->shape()));
162 painter->drawPath(path);
163 painter->restore();
165 //XXX this code is duplicated in the next function
166 QPointF basePoint = m_rect.topLeft() + QPointF(HANDLE_WIDTH / 2, HANDLE_WIDTH);
167 QPointF step = QPointF(0, ICON_SIZE + ICON_MARGIN);
168 QPointF separator = step + QPointF(0, ICON_MARGIN);
170 if (m_buttonsOnRight) {
171 basePoint += QPointF(m_rect.width() - ICON_SIZE - HANDLE_WIDTH, 0);
173 //end duplicate code
175 QPointF shiftC;
176 QPointF shiftD;
177 QPointF shiftR;
178 QPointF shiftM;
180 switch(m_pressedButton)
182 case ConfigureButton:
183 shiftC = QPointF(2, 2);
184 break;
185 case RemoveButton:
186 shiftD = QPointF(2, 2);
187 break;
188 case RotateButton:
189 shiftR = QPointF(2, 2);
190 break;
191 case ResizeButton:
192 shiftM = QPointF(2, 2);
193 break;
194 default:
195 break;
198 painter->drawPixmap(basePoint + shiftM, KIcon("transform-move").pixmap(ICON_SIZE, ICON_SIZE)); //FIXME no transform-resize icon
200 basePoint += step;
201 painter->drawPixmap(basePoint + shiftR, KIcon("transform-rotate").pixmap(ICON_SIZE, ICON_SIZE));
203 if (m_applet && m_applet->hasConfigurationInterface()) {
204 basePoint += step;
205 painter->drawPixmap(basePoint + shiftC, KIcon("configure").pixmap(ICON_SIZE, ICON_SIZE));
208 basePoint += separator;
209 painter->drawPixmap(basePoint + shiftD, KIcon("edit-delete").pixmap(ICON_SIZE, ICON_SIZE));
211 painter->restore();
214 AppletHandle::ButtonType AppletHandle::mapToButton(const QPointF &point) const
216 //XXX this code is duplicated in the prev. function
217 QPointF basePoint = m_rect.topLeft() + QPointF(HANDLE_WIDTH / 2, HANDLE_WIDTH);
218 QPointF step = QPointF(0, ICON_SIZE + ICON_MARGIN);
219 QPointF separator = step + QPointF(0, ICON_MARGIN);
221 if (m_buttonsOnRight) {
222 basePoint += QPointF(m_rect.width() - ICON_SIZE - HANDLE_WIDTH, 0);
224 //end duplicate code
226 QPolygonF activeArea = QPolygonF(QRectF(basePoint, QSizeF(ICON_SIZE, ICON_SIZE)));
228 if (activeArea.containsPoint(point, Qt::OddEvenFill)) {
229 return ResizeButton;
232 activeArea.translate(step);
233 if (activeArea.containsPoint(point, Qt::OddEvenFill)) {
234 return RotateButton;
237 if (m_applet && m_applet->hasConfigurationInterface()) {
238 activeArea.translate(step);
239 if (activeArea.containsPoint(point, Qt::OddEvenFill)) {
240 return ConfigureButton;
244 activeArea.translate(separator);
245 if (activeArea.containsPoint(point, Qt::OddEvenFill)) {
246 return RemoveButton;
249 return MoveButton;
250 //return m_applet->mapToParent(m_applet->shape()).contains(point) ? NoButton : MoveButton;
253 void AppletHandle::mousePressEvent(QGraphicsSceneMouseEvent *event)
255 if (m_pendingFade) {
256 //m_pendingFade = false;
257 return;
260 if (event->button() == Qt::LeftButton) {
261 m_pressedButton = mapToButton(event->pos());
262 //kDebug() << "button pressed:" << m_pressedButton;
263 if (m_pressedButton != NoButton) {
264 // when the mouse goes over a window, the applet is likely to
265 // get a hover out event that we really don't want to respond to
266 // so while we have a button pressed we intercept these events
267 // and handle them ourselves here in AppletHandle
268 m_applet->installSceneEventFilter(this);
269 m_applet->raise();
270 setZValue(m_applet->zValue());
272 event->accept();
273 update();
274 return;
277 QGraphicsItem::mousePressEvent(event);
280 void AppletHandle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
282 //kDebug() << "button pressed:" << m_pressedButton << ", fade pending?" << m_pendingFade;
283 if (m_applet) {
284 m_applet->removeSceneEventFilter(this);
287 if (m_pendingFade) {
288 startFading(FadeOut);
289 m_pendingFade = false;
292 ButtonType releasedAtButton = mapToButton(event->pos());
294 if (m_applet && event->button() == Qt::LeftButton) {
295 switch (m_pressedButton) {
296 case ResizeButton:
297 case RotateButton: {
298 if (m_scaleWidth > 0 && m_scaleHeight > 0) {
299 QRectF rect(m_applet->boundingRect());
300 const qreal newWidth = rect.width() * m_scaleWidth;
301 const qreal newHeight = rect.height() * m_scaleHeight;
302 m_applet->resetTransform();
303 m_applet->resize(newWidth, newHeight);
304 scale(1.0/m_scaleWidth, 1.0/m_scaleHeight);
305 moveBy((rect.width() - newWidth) / 2, (rect.height() - newHeight) / 2);
306 m_scaleWidth = m_scaleHeight = 0;
308 QRectF rect = QRectF(m_applet->pos(), m_applet->size());
309 QPointF center = rect.center();
311 m_angle += m_tempAngle;
312 m_tempAngle = 0;
314 QTransform matrix;
315 matrix.translate(center.x(), center.y());
316 matrix.rotateRadians(m_angle);
317 matrix.translate(-center.x(), -center.y());
319 setTransform(matrix);
320 m_applet->update();
321 break;
323 case ConfigureButton:
324 //FIXME: Remove this call once the configuration management change was done
325 if (m_pressedButton == releasedAtButton) {
326 m_containment->emitLaunchActivated();
327 m_applet->showConfigurationInterface();
329 break;
330 case RemoveButton:
331 if (m_pressedButton == releasedAtButton) {
332 forceDisappear();
333 Phase::self()->animateItem(m_applet, Phase::Disappear);
335 break;
336 case MoveButton: {
337 //find out if we were dropped on a panel or something
338 QWidget *w = QApplication::topLevelAt(event->screenPos());
339 kDebug() << "move to widget" << w;
340 if (w) {
341 Plasma::View *v = qobject_cast<Plasma::View *>(w);
342 if (v) {
343 Containment *c = v->containment();
344 //XXX the dashboard view won't give us a containment. if it did, this could
345 //break shit.
346 if (c && c != m_containment) {
347 //we actually have been dropped on another containment, so move there
348 //we have a screenpos, we need a scenepos
349 //FIXME how reliable is this transform?
350 QPoint pos = v->mapFromGlobal(event->screenPos());
351 switchContainment(c, v->mapToScene(pos));
355 break;
357 default:
358 break;
362 m_pressedButton = NoButton;
363 update();
366 qreal _k_distanceForPoint(QPointF point)
368 return std::sqrt(point.x()*point.x()+point.y()*point.y());
371 qreal _k_angleForPoints(const QPointF &center, const QPointF &pt1, const QPointF &pt2)
373 QPointF vec1 = pt1 - center;
374 QPointF vec2 = pt2 - center;
376 qreal alpha = std::atan2(vec1.y(), vec1.x());
377 qreal beta = std::atan2(vec2.y(), vec2.x());
379 return beta - alpha;
382 void AppletHandle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
384 static const qreal snapAngle = M_PI_2 /* $i 3.14159 / 2.0 */;
386 if (!m_applet) {
387 QGraphicsItem::mouseMoveEvent(event);
388 return;
391 QPointF curPos = transform().map(event->pos());
392 QPointF lastPos = transform().map(event->lastPos());
393 QPointF delta = curPos-lastPos;
395 if (m_pressedButton == MoveButton) {
396 setPos(pos()+delta);
397 // test for containment change
398 if (!m_containment->sceneBoundingRect().contains(event->scenePos())) {
399 // see which containment it belongs to
400 Corona * corona = qobject_cast<Corona*>(scene());
401 if (corona) {
402 QList<Containment*> containments = corona->containments();
403 for (int i = 0; i < containments.size(); ++i) {
404 if (containments[i]->sceneBoundingRect().contains(event->scenePos())) {
405 // add the applet to the new containment
406 // and take it from the old one
407 //kDebug() << "moving to other containment with position" << pos() << event->scenePos();
408 //kDebug() << "position before reparenting" << pos() << scenePos();
409 switchContainment(containments[i], scenePos());
410 break;
415 } else if (m_pressedButton == RotateButton ||
416 m_pressedButton == ResizeButton) {
417 if (_k_distanceForPoint(delta) <= 1.0) {
418 return;
421 QPointF pressPos = mapFromScene(event->buttonDownScenePos(Qt::LeftButton));
423 QRectF rect = QRectF(m_applet->pos(), m_applet->size());
424 QPointF center = rect.center();
426 if (m_pressedButton == RotateButton) {
427 m_tempAngle = _k_angleForPoints(center, pressPos, event->pos());
429 if (fabs(remainder(m_angle+m_tempAngle, snapAngle)) < 0.15) {
430 m_tempAngle = m_tempAngle - remainder(m_angle+m_tempAngle, snapAngle);
433 m_scaleWidth = m_scaleHeight = 1.0;
434 } else {
435 qreal w = m_applet->size().width();
436 qreal h = m_applet->size().height();
437 QSizeF min = m_applet->minimumSize();
438 QSizeF max = m_applet->maximumSize();
440 // If the applet doesn't have a minimum size, calculate based on a
441 // minimum content area size of 16x16
442 if (min.isEmpty()) {
443 min = m_applet->boundingRect().size() - m_applet->contentRect().size();
444 min += QSizeF(16, 16);
447 bool ignoreAspectRatio = m_applet->aspectRatioMode() == Qt::IgnoreAspectRatio;
449 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
450 ignoreAspectRatio = !ignoreAspectRatio;
453 if (ignoreAspectRatio) {
454 // free resizing
455 qreal newScaleWidth = 0;
456 qreal newScaleHeight = 0;
458 QPointF startDistance(pressPos - center);
459 QPointF currentDistance(event->pos() - center);
460 newScaleWidth = currentDistance.x() / startDistance.x();
461 newScaleHeight = currentDistance.y() / startDistance.y();
463 if (qAbs(w - (newScaleWidth * w)) <= KGlobalSettings::dndEventDelay()) {
464 newScaleWidth = 1.0;
466 if (qAbs(h - (newScaleHeight * h)) <= KGlobalSettings::dndEventDelay()) {
467 newScaleHeight = 1.0;
470 if (newScaleHeight * h < min.height()) {
471 m_scaleHeight = min.height() / h;
472 } else if (newScaleHeight * h > max.height()) {
473 m_scaleHeight = max.height() / h;
474 } else {
475 m_scaleHeight = newScaleHeight;
477 if (newScaleWidth * w < min.width()) {
478 m_scaleWidth = min.width() / w;
479 } else if (newScaleWidth * w > max.width()) {
480 m_scaleWidth = max.width() / w;
481 } else {
482 m_scaleWidth = newScaleWidth;
484 } else {
485 // maintain aspect ratio
486 qreal newScale = 0;
488 newScale = _k_distanceForPoint(event->pos()-center) / _k_distanceForPoint(pressPos-center);
489 if (qAbs(h - (newScale * h)) <= KGlobalSettings::dndEventDelay()) {
490 newScale = 1.0;
493 if (newScale * w < min.width() || newScale * h < min.height()) {
494 m_scaleWidth = m_scaleHeight = qMax(min.width() / w, min.height() / h);
495 } else if (newScale * w > max.width() && newScale * h > max.height()) {
496 m_scaleWidth = m_scaleHeight = qMin(max.width() / w, max.height() / h);
497 } else {
498 m_scaleHeight = m_scaleWidth = newScale;
503 QTransform matrix;
504 matrix.translate(center.x(), center.y());
505 matrix.rotateRadians(m_angle+m_tempAngle);
506 matrix.scale(m_scaleWidth, m_scaleHeight);
507 matrix.translate(-center.x(), -center.y());
508 setTransform(matrix);
509 } else {
510 QGraphicsItem::mouseMoveEvent(event);
514 //pos relative to scene
515 void AppletHandle::switchContainment(Containment *containment, const QPointF &pos)
517 Applet *applet=m_applet;
518 switch (containment->containmentType()) {
519 case Containment::PanelContainment:
520 kDebug() << "panel";
521 //we need to fully disassociate the applet and handle, then kill the handle
522 forceDisappear(); //takes care of event filter and killing handle
523 m_applet=0; //make sure we don't try to act on the applet again
524 applet->disconnect(this); //make sure the applet doesn't tell us to do anything
525 containment->addApplet(applet, containment->mapFromScene(pos));
526 break;
527 default: //FIXME assuming everything else behaves like desktop
528 kDebug() << "desktop";
529 m_containment = containment;
530 containment->addApplet(m_applet);
531 setParentItem(containment);
532 m_applet->setParentItem(this);
533 setPos(containment->mapFromScene(pos));
534 //setPos(pos);
536 update();
539 QVariant AppletHandle::itemChange(GraphicsItemChange change, const QVariant &value)
541 if (change == ItemPositionHasChanged && m_applet) {
542 m_applet->constraintsUpdated(Plasma::LocationConstraint);
544 return QGraphicsItem::itemChange(change, value);
547 void AppletHandle::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
549 Q_UNUSED(event);
550 //kDebug() << "hover enter";
551 m_hoverTimer->start();
554 void AppletHandle::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
556 Q_UNUSED(event);
557 //kDebug() << "hover leave" << m_pressedButton;
558 m_hoverTimer->stop();
560 if (m_pressedButton != NoButton) {
561 m_pendingFade = true;
562 } else {
563 startFading(FadeOut);
567 bool AppletHandle::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
569 if (watched == m_applet && event->type() == QEvent::GraphicsSceneHoverLeave) {
570 hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event));
571 return true;
574 return false;
577 void AppletHandle::fadeAnimation(qreal progress)
579 qreal endOpacity = 1.0;
580 if (m_anim == FadeOut) {
581 endOpacity = 0.0;
584 m_opacity += (endOpacity - m_opacity) * progress;
586 if (progress >= 1.0 && m_anim == FadeOut) {
587 if (m_applet) {
588 m_applet->removeSceneEventFilter(this);
591 emit disappearDone(this);
594 update();
597 void AppletHandle::fadeIn()
599 startFading(FadeIn);
602 void AppletHandle::appletDestroyed()
604 m_applet = 0;
605 deleteLater();
608 void AppletHandle::appletResized()
610 prepareGeometryChange();
611 calculateSize();
612 update();
615 void AppletHandle::startFading(FadeType anim)
617 if (m_animId != 0) {
618 Phase::self()->stopCustomAnimation(m_animId);
621 m_anim = anim;
622 qreal time = 250;
624 if (anim == FadeOut && m_hoverTimer->isActive()) {
625 // fading out before we've started fading in
626 m_hoverTimer->stop();
627 fadeAnimation(1.0);
628 return;
631 if (m_applet) {
632 m_applet->removeSceneEventFilter(this);
635 if (anim == FadeIn) {
636 time *= 1.0 - m_opacity;
637 } else {
638 time *= m_opacity;
641 m_animId = Phase::self()->customAnimation(40, (int)time, Phase::EaseInOutCurve, this, "fadeAnimation");
644 void AppletHandle::forceDisappear()
646 setAcceptsHoverEvents(false);
647 startFading(FadeOut);
650 void AppletHandle::calculateSize()
652 m_rect = m_applet->boundingRect();
653 m_rect = m_applet->mapToParent(m_rect).boundingRect();
655 //XXX remember to update this if the number of buttons changes
656 const int requiredHeight = (HANDLE_WIDTH * 2) + m_applet->hasConfigurationInterface() ?
657 ((ICON_SIZE + ICON_MARGIN) * 4) :
658 ((ICON_SIZE + ICON_MARGIN) * 3) +
659 ICON_MARGIN; // that last margin is blank space before the close button
660 if (m_rect.height() < requiredHeight) {
661 float delta = requiredHeight - m_rect.height();
662 delta = delta/2.0;
663 if (delta > 0.0) {
664 m_rect.adjust(0.0, -delta, 0.0, delta);
668 m_rect.adjust(-HANDLE_WIDTH, -HANDLE_WIDTH, HANDLE_WIDTH, HANDLE_WIDTH);
670 if (m_applet->pos().x() <= ((HANDLE_WIDTH * 2) + ICON_SIZE)) {
671 m_rect.adjust(0.0, 0.0, ICON_SIZE, 0.0);
672 m_buttonsOnRight = true;
673 } else {
674 m_rect.adjust(- ICON_SIZE, 0.0, 0.0, 0.0);
680 #include "applethandle_p.moc"