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>
36 #include "containment.h"
44 qreal
_k_angleForPoints(const QPointF
¢er
, const QPointF
&pt1
, const QPointF
&pt2
);
46 AppletHandle::AppletHandle(Containment
*parent
, Applet
*applet
)
48 QGraphicsItem(parent
),
49 m_pressedButton(NoButton
),
50 m_containment(parent
),
59 m_buttonsOnRight(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),
75 QPointF(cosine
, sine
));
77 m_applet
->resetTransform();
80 m_applet
->setParentItem(this);
82 rect
= QRectF(m_applet
->pos(), m_applet
->size());
83 center
= rect
.center();
85 matrix
.translate(center
.x(), center
.y());
86 matrix
.rotateRadians(m_angle
);
87 matrix
.translate(-center
.x(), -center
.y());
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()
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
));
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
130 QRectF
Plasma::AppletHandle::boundingRect() const
135 void AppletHandle::paint(QPainter
*painter
, const QStyleOptionGraphicsItem
*option
, QWidget
*widget
)
141 painter
->setOpacity(m_opacity
);
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);
155 QPainterPath shape
= m_applet
->shape();
157 if (!shape
.isEmpty()) {
158 path
= path
.subtracted(m_applet
->mapToParent(m_applet
->shape()));
162 painter
->drawPath(path
);
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);
180 switch(m_pressedButton
)
182 case ConfigureButton
:
183 shiftC
= QPointF(2, 2);
186 shiftD
= QPointF(2, 2);
189 shiftR
= QPointF(2, 2);
192 shiftM
= QPointF(2, 2);
198 painter
->drawPixmap(basePoint
+ shiftM
, KIcon("transform-move").pixmap(ICON_SIZE
, ICON_SIZE
)); //FIXME no transform-resize icon
201 painter
->drawPixmap(basePoint
+ shiftR
, KIcon("transform-rotate").pixmap(ICON_SIZE
, ICON_SIZE
));
203 if (m_applet
&& m_applet
->hasConfigurationInterface()) {
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
));
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);
226 QPolygonF activeArea
= QPolygonF(QRectF(basePoint
, QSizeF(ICON_SIZE
, ICON_SIZE
)));
228 if (activeArea
.containsPoint(point
, Qt::OddEvenFill
)) {
232 activeArea
.translate(step
);
233 if (activeArea
.containsPoint(point
, Qt::OddEvenFill
)) {
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
)) {
250 //return m_applet->mapToParent(m_applet->shape()).contains(point) ? NoButton : MoveButton;
253 void AppletHandle::mousePressEvent(QGraphicsSceneMouseEvent
*event
)
256 //m_pendingFade = false;
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);
270 setZValue(m_applet
->zValue());
277 QGraphicsItem::mousePressEvent(event
);
280 void AppletHandle::mouseReleaseEvent(QGraphicsSceneMouseEvent
*event
)
282 //kDebug() << "button pressed:" << m_pressedButton << ", fade pending?" << m_pendingFade;
284 m_applet
->removeSceneEventFilter(this);
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
) {
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
;
315 matrix
.translate(center
.x(), center
.y());
316 matrix
.rotateRadians(m_angle
);
317 matrix
.translate(-center
.x(), -center
.y());
319 setTransform(matrix
);
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();
331 if (m_pressedButton
== releasedAtButton
) {
333 Phase::self()->animateItem(m_applet
, Phase::Disappear
);
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
;
341 Plasma::View
*v
= qobject_cast
<Plasma::View
*>(w
);
343 Containment
*c
= v
->containment();
344 //XXX the dashboard view won't give us a containment. if it did, this could
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
));
362 m_pressedButton
= NoButton
;
366 qreal
_k_distanceForPoint(QPointF point
)
368 return std::sqrt(point
.x()*point
.x()+point
.y()*point
.y());
371 qreal
_k_angleForPoints(const QPointF
¢er
, 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());
382 void AppletHandle::mouseMoveEvent(QGraphicsSceneMouseEvent
*event
)
384 static const qreal snapAngle
= M_PI_2
/* $i 3.14159 / 2.0 */;
387 QGraphicsItem::mouseMoveEvent(event
);
391 QPointF curPos
= transform().map(event
->pos());
392 QPointF lastPos
= transform().map(event
->lastPos());
393 QPointF delta
= curPos
-lastPos
;
395 if (m_pressedButton
== MoveButton
) {
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());
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());
415 } else if (m_pressedButton
== RotateButton
||
416 m_pressedButton
== ResizeButton
) {
417 if (_k_distanceForPoint(delta
) <= 1.0) {
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;
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
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
) {
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()) {
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
;
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
;
482 m_scaleWidth
= newScaleWidth
;
485 // maintain aspect ratio
488 newScale
= _k_distanceForPoint(event
->pos()-center
) / _k_distanceForPoint(pressPos
-center
);
489 if (qAbs(h
- (newScale
* h
)) <= KGlobalSettings::dndEventDelay()) {
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
);
498 m_scaleHeight
= m_scaleWidth
= newScale
;
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
);
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
:
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
));
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
));
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
)
550 //kDebug() << "hover enter";
551 m_hoverTimer
->start();
554 void AppletHandle::hoverLeaveEvent(QGraphicsSceneHoverEvent
*event
)
557 //kDebug() << "hover leave" << m_pressedButton;
558 m_hoverTimer
->stop();
560 if (m_pressedButton
!= NoButton
) {
561 m_pendingFade
= true;
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
));
577 void AppletHandle::fadeAnimation(qreal progress
)
579 qreal endOpacity
= 1.0;
580 if (m_anim
== FadeOut
) {
584 m_opacity
+= (endOpacity
- m_opacity
) * progress
;
586 if (progress
>= 1.0 && m_anim
== FadeOut
) {
588 m_applet
->removeSceneEventFilter(this);
591 emit
disappearDone(this);
597 void AppletHandle::fadeIn()
602 void AppletHandle::appletDestroyed()
608 void AppletHandle::appletResized()
610 prepareGeometryChange();
615 void AppletHandle::startFading(FadeType anim
)
618 Phase::self()->stopCustomAnimation(m_animId
);
624 if (anim
== FadeOut
&& m_hoverTimer
->isActive()) {
625 // fading out before we've started fading in
626 m_hoverTimer
->stop();
632 m_applet
->removeSceneEventFilter(this);
635 if (anim
== FadeIn
) {
636 time
*= 1.0 - 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();
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;
674 m_rect
.adjust(- ICON_SIZE
, 0.0, 0.0, 0.0);
680 #include "applethandle_p.moc"