2 * Copyright 2008 by Montel Laurent <montel@kde.org>
3 * Copyright 2008 by Marco Martin <notmart@gmail.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
21 #include "popupapplet.h"
22 #include "private/popupapplet_p.h"
24 #include <QGraphicsProxyWidget>
25 #include <QGraphicsLinearLayout>
26 #include <QVBoxLayout>
28 #include <QApplication>
31 #include <kiconloader.h>
32 #include <kwindowsystem.h>
33 #include <kglobalsettings.h>
35 #include "plasma/private/applet_p.h"
36 #include "plasma/private/extenderitemmimedata_p.h"
37 #include "plasma/corona.h"
38 #include "plasma/containment.h"
39 #include "plasma/dialog.h"
40 #include "plasma/extender.h"
41 #include "plasma/extenderitem.h"
42 #include "plasma/tooltipmanager.h"
43 #include "plasma/widgets/iconwidget.h"
48 PopupApplet::PopupApplet(QObject
*parent
, const QVariantList
&args
)
49 : Plasma::Applet(parent
, args
),
50 d(new PopupAppletPrivate(this))
52 int iconSize
= IconSize(KIconLoader::Desktop
);
53 resize(iconSize
, iconSize
);
54 connect(this, SIGNAL(activate()), this, SLOT(internalTogglePopup()));
58 PopupApplet::~PopupApplet()
64 void PopupApplet::setPopupIcon(const QIcon
&icon
)
71 setAspectRatioMode(d
->savedAspectRatio
);
78 d
->icon
= new Plasma::IconWidget(icon
, QString(), this);
79 connect(d
->icon
, SIGNAL(clicked()), this, SLOT(internalTogglePopup()));
81 QGraphicsLinearLayout
*layout
= new QGraphicsLinearLayout();
82 layout
->setContentsMargins(0, 0, 0, 0);
83 layout
->setSpacing(0);
84 layout
->setOrientation(Qt::Horizontal
);
86 if (formFactor() == Plasma::Vertical
|| formFactor() == Plasma::Horizontal
) {
87 d
->savedAspectRatio
= aspectRatioMode();
88 setAspectRatioMode(Plasma::ConstrainedSquare
);
93 d
->icon
->setIcon(icon
);
97 void PopupApplet::setPopupIcon(const QString
&iconName
)
99 setPopupIcon(KIcon(iconName
));
102 QIcon
PopupApplet::popupIcon() const
104 return d
->icon
? d
->icon
->icon() : QIcon();
107 QWidget
*PopupApplet::widget()
112 QGraphicsWidget
*PopupApplet::graphicsWidget()
114 return static_cast<Applet
*>(this)->d
->extender
;
117 void PopupAppletPrivate::checkExtenderAppearance(Plasma::FormFactor f
)
119 Extender
*extender
= qobject_cast
<Extender
*>(q
->graphicsWidget());
121 if (f
!= Plasma::Horizontal
&& f
!= Plasma::Vertical
) {
122 extender
->setAppearance(Extender::NoBorders
);
123 } else if (q
->location() == TopEdge
) {
124 extender
->setAppearance(Extender::TopDownStacked
);
126 extender
->setAppearance(Extender::BottomUpStacked
);
130 dialog
->setGraphicsWidget(extender
);
135 void PopupAppletPrivate::popupConstraintsEvent(Plasma::Constraints constraints
)
137 Plasma::FormFactor f
= q
->formFactor();
139 if (constraints
& Plasma::LocationConstraint
) {
140 checkExtenderAppearance(f
);
143 if (constraints
& Plasma::FormFactorConstraint
||
144 constraints
& Plasma::StartupCompletedConstraint
||
145 (constraints
& Plasma::SizeConstraint
&&
146 (f
== Plasma::Vertical
|| f
== Plasma::Horizontal
))) {
147 QGraphicsLinearLayout
*lay
= dynamic_cast<QGraphicsLinearLayout
*>(q
->layout());
149 if (icon
&& !icon
->icon().isNull() && lay
) {
154 QSizeF containmentSize
;
156 QGraphicsWidget
*gWidget
= q
->graphicsWidget();
157 kDebug() << "graphics widget is" << (QObject
*)gWidget
;
158 QWidget
*qWidget
= q
->widget();
161 minimum
= gWidget
->minimumSize();
162 // our layout may have been replaced on us in the call to graphicsWidget!
163 lay
= dynamic_cast<QGraphicsLinearLayout
*>(q
->layout());
165 if (!(constraints
& LocationConstraint
)) {
166 checkExtenderAppearance(f
);
168 } else if (qWidget
) {
169 minimum
= qWidget
->minimumSizeHint();
172 if (q
->containment()) {
173 containmentSize
= q
->containment()->size();
177 if (icon
&& !icon
->icon().isNull() && ((f
!= Plasma::Vertical
&& f
!= Plasma::Horizontal
) ||
178 ((f
== Plasma::Vertical
&& containmentSize
.width() >= minimum
.width()) ||
179 (f
== Plasma::Horizontal
&& containmentSize
.height() >= minimum
.height())))) {
180 kDebug() << "we are expanding the popupapplet";
182 // we only switch to expanded if we aren't horiz/vert constrained and
183 // this applet has an icon.
184 // otherwise, we leave it up to the applet itself to figure it out
189 if (savedAspectRatio
!= Plasma::InvalidAspectRatioMode
) {
190 q
->setAspectRatioMode(savedAspectRatio
);
194 if (dialog
->layout() && qWidget
) {
195 //we don't want to delete Widget inside the dialog layout
196 dialog
->layout()->removeWidget(qWidget
);
200 qWidget
->setParent(0);
208 lay
= new QGraphicsLinearLayout();
209 lay
->setContentsMargins(0, 0, 0, 0);
211 lay
->setOrientation(Qt::Horizontal
);
218 Corona
*corona
= qobject_cast
<Corona
*>(gWidget
->scene());
221 corona
->removeOffscreenWidget(gWidget
);
224 lay
->addItem(gWidget
);
225 prefSize
= gWidget
->preferredSize().toSize();
226 } else if (qWidget
) {
228 proxy
= new QGraphicsProxyWidget(q
);
229 proxy
->setWidget(qWidget
);
234 prefSize
= qWidget
->sizeHint();
237 //we could be on a big panel, but in that case we will be able to resize
238 //more than the natural minimum size, because we'll transform into an icon
239 if (f
== Plasma::Horizontal
) {
240 minimum
.setHeight(0);
241 } else if (f
== Plasma::Vertical
) {
245 qreal left
, top
, right
, bottom
;
246 q
->getContentsMargins(&left
, &top
, &right
, &bottom
);
247 QSizeF
oldSize(q
->size());
248 q
->setMinimumSize(minimum
+ QSizeF(left
+right
, top
+bottom
));
249 //size not saved/invalid size saved
250 if (oldSize
.width() < q
->minimumSize().width() || oldSize
.height() < q
->minimumSize().height()) {
252 emit q
->appletTransformedItself();
256 kDebug() << "about to switch to a popup";
257 //save the aspect ratio mode in case we drag'n drop in the Desktop later
258 savedAspectRatio
= q
->aspectRatioMode();
262 q
->setAspectRatioMode(Plasma::ConstrainedSquare
);
266 proxy
->setWidget(0); // prevent it from deleting our widget!
272 dialog
= new Plasma::Dialog();
274 //no longer use Qt::Popup since that seems to cause a lot of problem when you drag
275 //stuff out of your Dialog (extenders). Monitor WindowDeactivate events so we can
276 //emulate the same kind of behavior as Qt::Popup (close when you click somewhere
278 Qt::WindowFlags wflags
= Qt::FramelessWindowHint
| Qt::WindowStaysOnTopHint
;
281 wflags
|= Qt::X11BypassWindowManagerHint
;
284 dialog
->setWindowFlags(wflags
);
285 KWindowSystem::setState(dialog
->winId(), NET::SkipTaskbar
| NET::SkipPager
);
286 dialog
->installEventFilter(q
);
288 q
->setMinimumSize(QSize(0, 0));
290 Corona
*corona
= qobject_cast
<Corona
*>(gWidget
->scene());
292 //could that cast ever fail??
294 corona
->addOffscreenWidget(gWidget
);
295 dialog
->setGraphicsWidget(gWidget
);
296 gWidget
->resize(gWidget
->preferredSize());
298 } else if (qWidget
) {
299 QVBoxLayout
*l_layout
= new QVBoxLayout(dialog
);
300 l_layout
->setSpacing(0);
301 l_layout
->setMargin(0);
302 l_layout
->addWidget(qWidget
);
303 dialog
->adjustSize();
306 QObject::connect(dialog
, SIGNAL(dialogResized()), q
, SLOT(dialogSizeChanged()));
307 QObject::connect(dialog
, SIGNAL(dialogVisible(bool)), q
, SLOT(dialogStatusChanged(bool)));
314 q
->setMinimumSize(0,0);
319 void PopupApplet::mousePressEvent(QGraphicsSceneMouseEvent
*event
)
321 if (!d
->icon
&& !d
->popupLostFocus
&& event
->buttons() == Qt::LeftButton
) {
322 d
->clicked
= scenePos().toPoint();
323 event
->setAccepted(true);
326 d
->popupLostFocus
= false;
327 Applet::mousePressEvent(event
);
331 void PopupApplet::mouseReleaseEvent(QGraphicsSceneMouseEvent
*event
)
334 (d
->clicked
- scenePos().toPoint()).manhattanLength() < KGlobalSettings::dndEventDelay()) {
335 d
->internalTogglePopup();
337 Applet::mouseReleaseEvent(event
);
341 bool PopupApplet::eventFilter(QObject
*watched
, QEvent
*event
)
343 if (watched
== d
->dialog
&& (event
->type() == QEvent::WindowDeactivate
)) {
344 d
->popupLostFocus
= true;
346 QTimer::singleShot(100, this, SLOT(clearPopupLostFocus()));
350 if (layout() && watched == graphicsWidget() && (event->type() == QEvent::GraphicsSceneResize)) {
351 //sizes are recalculated in the constraintsevent so let's just call that.
352 d->popupConstraintsEvent(Plasma::FormFactorConstraint);
354 //resize vertically if necesarry.
355 if (formFactor() == Plasma::MediaCenter || formFactor() == Plasma::Planar) {
356 resize(QSizeF(size().width(), minimumHeight()));
361 return Applet::eventFilter(watched
, event
);
364 //FIXME: some duplication between the drag events... maybe add some simple helper function?
365 void PopupApplet::dragEnterEvent(QGraphicsSceneDragDropEvent
*event
)
367 if (event
->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) {
368 const ExtenderItemMimeData
*mimeData
=
369 qobject_cast
<const ExtenderItemMimeData
*>(event
->mimeData());
370 if (mimeData
&& qobject_cast
<Extender
*>(graphicsWidget())) {
377 void PopupApplet::dragLeaveEvent(QGraphicsSceneDragDropEvent
*event
)
379 if (event
->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) {
380 const ExtenderItemMimeData
*mimeData
=
381 qobject_cast
<const ExtenderItemMimeData
*>(event
->mimeData());
382 if (mimeData
&& qobject_cast
<Extender
*>(graphicsWidget())) {
383 //We want to hide the popup if we're not moving onto the popup AND it is not the popup
385 if (d
->dialog
&& !d
->dialog
->geometry().contains(event
->screenPos()) &&
386 mimeData
->extenderItem()->extender() != qobject_cast
<Extender
*>(graphicsWidget())) {
387 //We actually try to hide the popup, with a call to showPopup, with a smal timeout,
388 //so if the user moves into the popup fast enough, it remains open (the extender
389 //will call showPopup which will cancel the timeout.
396 void PopupApplet::dropEvent(QGraphicsSceneDragDropEvent
*event
)
398 if (event
->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) {
399 const ExtenderItemMimeData
*mimeData
=
400 qobject_cast
<const ExtenderItemMimeData
*>(event
->mimeData());
401 if (mimeData
&& qobject_cast
<Extender
*>(graphicsWidget())) {
402 mimeData
->extenderItem()->setExtender(extender());
403 QApplication::restoreOverrideCursor();
408 void PopupApplet::showPopup(uint popupDuration
)
411 // move the popup before its fist show, even if the show isn't triggered by
412 // a click, this should fix the first random position seen in some widgets
413 if (!d
->dialog
->isVisible()) {
414 d
->internalTogglePopup();
421 if (popupDuration
> 0) {
423 d
->timer
= new QTimer(this);
424 connect(d
->timer
, SIGNAL(timeout()), this, SLOT(hideTimedPopup()));
427 d
->timer
->start(popupDuration
);
432 void PopupApplet::hidePopup()
439 void PopupApplet::togglePopup()
441 d
->internalTogglePopup();
444 Plasma::PopupPlacement
PopupApplet::popupPlacement() const
446 return d
->popupPlacement
;
449 void PopupApplet::popupEvent(bool)
453 void PopupApplet::setPassivePopup(bool passive
)
455 d
->passive
= passive
;
458 Qt::WindowFlags wflags
= Qt::FramelessWindowHint
| Qt::WindowStaysOnTopHint
;
461 wflags
|= Qt::X11BypassWindowManagerHint
;
464 d
->dialog
->setWindowFlags(wflags
);
468 bool PopupApplet::isPassivePopup() const
473 bool PopupApplet::isPopupShowing() const
475 return d
->dialog
&& d
->dialog
->isVisible();
478 PopupAppletPrivate::PopupAppletPrivate(PopupApplet
*applet
)
483 popupPlacement(Plasma::FloatingPopup
),
484 savedAspectRatio(Plasma::InvalidAspectRatioMode
),
486 popupLostFocus(false),
491 PopupAppletPrivate::~PopupAppletPrivate()
501 void PopupAppletPrivate::internalTogglePopup()
511 if (dialog
->isVisible()) {
514 ToolTipManager::self()->hide(q
);
515 updateDialogPosition();
516 KWindowSystem::setState(dialog
->winId(), NET::SkipTaskbar
| NET::SkipPager
);
520 dialog
->clearFocus();
523 void PopupAppletPrivate::hideTimedPopup()
529 void PopupAppletPrivate::clearPopupLostFocus()
531 popupLostFocus
= false;
534 void PopupAppletPrivate::dialogSizeChanged()
536 //Reposition the dialog
538 KConfigGroup
*mainGroup
= static_cast<Applet
*>(q
)->d
->mainConfigGroup();
539 KConfigGroup
sizeGroup(mainGroup
, "PopupApplet");
540 sizeGroup
.writeEntry("DialogHeight", dialog
->height());
541 sizeGroup
.writeEntry("DialogWidth", dialog
->width());
543 updateDialogPosition();
545 emit q
->configNeedsSaving();
549 void PopupAppletPrivate::dialogStatusChanged(bool status
)
551 q
->popupEvent(status
);
554 void PopupAppletPrivate::updateDialogPosition()
556 QGraphicsView
*view
= q
->view();
562 KConfigGroup
*mainGroup
= static_cast<Applet
*>(q
)->d
->mainConfigGroup();
563 KConfigGroup
sizeGroup(mainGroup
, "PopupApplet");
565 Q_ASSERT(q
->containment());
566 Q_ASSERT(q
->containment()->corona());
568 int preferredWidth
= 0;
569 int preferredHeight
= 0;
570 if (dialog
->graphicsWidget()) {
571 preferredWidth
= dialog
->graphicsWidget()->preferredSize().width();
572 preferredHeight
= dialog
->graphicsWidget()->preferredSize().height();
575 const int width
= qMin(sizeGroup
.readEntry("DialogWidth", preferredWidth
),
576 q
->containment()->corona()->screenGeometry(-1).width() - 50);
577 const int height
= qMin(sizeGroup
.readEntry("DialogHeight", preferredHeight
),
578 q
->containment()->corona()->screenGeometry(-1).height() - 50);
580 QSize
saved(width
, height
);
582 if (saved
.isNull()) {
583 saved
= dialog
->sizeHint();
585 saved
= saved
.expandedTo(dialog
->minimumSizeHint());
588 if (saved
.width() != dialog
->width() || saved
.height() != dialog
->height()) {
589 dialog
->resize(saved
);
592 QSize s
= dialog
->size();
593 QPoint pos
= view
->mapFromScene(q
->scenePos());
595 //try to access a corona
596 Corona
*corona
= qobject_cast
<Corona
*>(q
->scene());
598 pos
= corona
->popupPosition(q
, s
);
601 bool reverse
= false;
602 if (q
->formFactor() == Plasma::Vertical
) {
603 if (view
->mapToGlobal(view
->mapFromScene(q
->scenePos())).y() + q
->size().height()/2 < pos
.y() + dialog
->size().width()/2) {
607 if (view
->mapToGlobal(view
->mapFromScene(q
->scenePos())).x() + q
->size().width()/2 < pos
.x() + dialog
->size().width()/2) {
612 switch (q
->location()) {
614 if (pos
.x() >= q
->pos().x()) {
615 dialog
->setResizeHandleCorners(Dialog::NorthEast
);
617 dialog
->setResizeHandleCorners(Dialog::NorthWest
);
621 popupPlacement
= Plasma::TopPosedLeftAlignedPopup
;
623 popupPlacement
= Plasma::TopPosedRightAlignedPopup
;
627 if (pos
.x() >= q
->pos().x()) {
628 dialog
->setResizeHandleCorners(Dialog::SouthEast
);
630 dialog
->setResizeHandleCorners(Dialog::SouthWest
);
634 popupPlacement
= Plasma::BottomPosedLeftAlignedPopup
;
636 popupPlacement
= Plasma::BottomPosedRightAlignedPopup
;
640 if (pos
.y() >= q
->pos().y()) {
641 dialog
->setResizeHandleCorners(Dialog::SouthEast
);
643 dialog
->setResizeHandleCorners(Dialog::NorthEast
);
647 popupPlacement
= Plasma::RightPosedTopAlignedPopup
;
649 popupPlacement
= Plasma::RightPosedBottomAlignedPopup
;
654 if (pos
.y() >= q
->pos().y()) {
655 dialog
->setResizeHandleCorners(Dialog::SouthWest
);
657 dialog
->setResizeHandleCorners(Dialog::NorthWest
);
661 popupPlacement
= Plasma::LeftPosedTopAlignedPopup
;
663 popupPlacement
= Plasma::LeftPosedBottomAlignedPopup
;
667 dialog
->setResizeHandleCorners(Dialog::NorthEast
);
672 } // Plasma namespace
674 #include "popupapplet.moc"