2 * Copyright 2007 by Alex Merry <alex.merry@kdemail.net>
3 * Copyright 2008 by Alexis Ménard <darktears31@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Library General Public License version 2,
7 * or (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.
24 #include <QApplication>
25 #include <QGraphicsLinearLayout>
28 #include <QDesktopWidget>
29 #include <QGridLayout>
33 #include <QGraphicsLayout>
34 #include <QGraphicsSceneDragDropEvent>
40 #include <KIntNumInput>
41 #include <KMessageBox>
43 #include <Plasma/Corona>
44 #include <Plasma/FrameSvg>
45 #include <Plasma/Theme>
46 #include <Plasma/View>
47 #include <Plasma/PaintUtils>
49 #include <kephal/screens.h>
51 using namespace Plasma
;
53 class Spacer
: public QGraphicsWidget
56 Spacer(QGraphicsWidget
*parent
)
57 : QGraphicsWidget(parent
)
68 void dropEvent(QGraphicsSceneDragDropEvent
*event
)
70 event
->setPos(mapToParent(event
->pos()));
71 panel
->dropEvent(event
);
74 void paint(QPainter
*painter
, const QStyleOptionGraphicsItem
*option
, QWidget
* widget
= 0)
79 //TODO: make this a pretty gradient?
80 painter
->setRenderHint(QPainter::Antialiasing
);
81 QPainterPath p
= Plasma::PaintUtils::roundedRectangle(contentsRect().adjusted(1, 1, -2, -2), 4);
82 QColor c
= Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor
);
85 painter
->fillPath(p
, c
);
89 Panel::Panel(QObject
*parent
, const QVariantList
&args
)
90 : Containment(parent
, args
),
93 m_currentSize(QSize(Kephal::ScreenUtils::screenSize(screen()).width(), 35)),
98 m_background
= new Plasma::FrameSvg(this);
99 m_background
->setImagePath("widgets/panel-background");
100 m_background
->setEnabledBorders(Plasma::FrameSvg::AllBorders
);
101 connect(m_background
, SIGNAL(repaintNeeded()), this, SLOT(backgroundChanged()));
103 setContainmentType(Containment::PanelContainment
);
104 resize(m_currentSize
);
105 setMinimumSize(m_currentSize
);
106 setMaximumSize(m_currentSize
);
108 connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeUpdated()));
109 connect(this, SIGNAL(appletAdded(Plasma::Applet
*,QPointF
)),
110 this, SLOT(layoutApplet(Plasma::Applet
*,QPointF
)));
111 connect(this, SIGNAL(appletRemoved(Plasma::Applet
*)),
112 this, SLOT(appletRemoved(Plasma::Applet
*)));
122 //FIXME: This should be enabled, but in that case proxywidgets won't get rendered
123 //setFlag(ItemClipsChildrenToShape, true);
125 KConfigGroup cg
= config("Configuration");
127 m_currentSize
= cg
.readEntry("minimumSize", m_currentSize
);
128 if (formFactor() == Plasma::Vertical
) {
129 m_currentSize
.expandedTo(QSize(0, 35));
131 m_currentSize
.expandedTo(QSize(35, 0));
134 setMinimumSize(cg
.readEntry("minimumSize", m_currentSize
));
135 setMaximumSize(cg
.readEntry("maximumSize", m_currentSize
));
136 setDrawWallpaper(false);
139 QList
<QAction
*> Panel::contextualActions()
141 if (!m_configureAction
) {
142 m_configureAction
= new QAction(i18n("Panel Settings"), this);
143 m_configureAction
->setIcon(KIcon("configure"));
144 connect(m_configureAction
, SIGNAL(triggered()), this, SIGNAL(toolBoxToggled()));
146 m_addPanelAction
= new QAction(i18n("Add Panel"), this);
147 connect(m_addPanelAction
, SIGNAL(triggered(bool)), this, SLOT(addPanel()));
148 m_addPanelAction
->setIcon(KIcon("list-add"));
149 constraintsEvent(Plasma::ImmutableConstraint
);
152 QList
<QAction
*> actions
;
153 actions
<< action("add widgets") << m_addPanelAction
<< action("lock widgets") << m_configureAction
<< action("remove");
157 void Panel::backgroundChanged()
159 constraintsEvent(Plasma::LocationConstraint
);
162 void Panel::layoutApplet(Plasma::Applet
* applet
, const QPointF
&pos
)
164 // this gets called whenever an applet is added, and we add it to our layout
165 QGraphicsLinearLayout
*lay
= dynamic_cast<QGraphicsLinearLayout
*>(layout());
171 Plasma::FormFactor f
= formFactor();
172 int insertIndex
= -1;
174 //Enlarge the panel if possible and needed
175 QSizeF appletHint
= applet
->preferredSize();
176 QSizeF panelHint
= layout()->preferredSize();
177 if (f
== Plasma::Horizontal
) {
178 if (panelHint
.width() + appletHint
.width() > size().width()) {
179 resize(panelHint
.width() + appletHint
.width(), size().height());
182 if (panelHint
.height() + appletHint
.height() > size().height()) {
183 resize(size().width(), panelHint
.height() + appletHint
.height());
186 layout()->setMaximumSize(size());
188 //if pos is (-1,-1) insert at the end of the panel
189 if (pos
!= QPoint(-1, -1)) {
190 for (int i
= 0; i
< lay
->count(); ++i
) {
191 QRectF siblingGeometry
= lay
->itemAt(i
)->geometry();
192 if (f
== Plasma::Horizontal
) {
193 qreal middle
= (siblingGeometry
.left() + siblingGeometry
.right()) / 2.0;
194 if (pos
.x() < middle
) {
197 } else if (pos
.x() <= siblingGeometry
.right()) {
201 } else { // Plasma::Vertical
202 qreal middle
= (siblingGeometry
.top() + siblingGeometry
.bottom()) / 2.0;
203 if (pos
.y() < middle
) {
206 } else if (pos
.y() <= siblingGeometry
.bottom()) {
214 if (insertIndex
== -1) {
215 lay
->addItem(applet
);
217 lay
->insertItem(insertIndex
, applet
);
220 connect(applet
, SIGNAL(sizeHintChanged(Qt::SizeHint
)), this, SLOT(updateSize()));
223 void Panel::appletRemoved(Plasma::Applet
* applet
)
225 QGraphicsLinearLayout
*lay
= dynamic_cast<QGraphicsLinearLayout
*>(layout());
226 lay
->removeItem(applet
);
228 //shrink the panel if possible
229 if (formFactor() == Plasma::Horizontal
) {
230 resize(size().width() - applet
->size().width(), size().height());
232 resize(size().width(), size().height() - applet
->size().height());
234 layout()->setMaximumSize(size());
237 void Panel::updateSize()
239 Plasma::Applet
*applet
= qobject_cast
<Plasma::Applet
*>(sender());
242 if (formFactor() == Plasma::Horizontal
) {
243 const int delta
= applet
->preferredWidth() - applet
->size().width();
244 //setting the preferred width when delta = 0 and preferredWidth() < minimumWidth()
245 // leads to the same thing as setPreferredWidth(minimumWidth())
247 setPreferredWidth(preferredWidth() + delta
);
249 } else if (formFactor() == Plasma::Vertical
) {
250 const int delta
= applet
->preferredHeight() - applet
->size().height();
252 setPreferredHeight(preferredHeight() + delta
);
256 resize(preferredSize());
260 void Panel::addPanel()
263 Containment
* panel
= corona()->addContainment("panel");
264 panel
->showConfigurationInterface();
266 panel
->setScreen(screen());
268 QList
<Plasma::Location
> freeEdges
= corona()->freeEdges(screen());
269 kDebug() << freeEdges
;
270 Plasma::Location destination
;
271 if (freeEdges
.contains(Plasma::TopEdge
)) {
272 destination
= Plasma::TopEdge
;
273 } else if (freeEdges
.contains(Plasma::BottomEdge
)) {
274 destination
= Plasma::BottomEdge
;
275 } else if (freeEdges
.contains(Plasma::LeftEdge
)) {
276 destination
= Plasma::LeftEdge
;
277 } else if (freeEdges
.contains(Plasma::RightEdge
)) {
278 destination
= Plasma::RightEdge
;
279 } else destination
= Plasma::TopEdge
;
281 panel
->setLocation(destination
);
283 // trigger an instant layout so we immediately have a proper geometry
284 // rather than waiting around for the event loop
285 panel
->updateConstraints(Plasma::StartupCompletedConstraint
);
286 panel
->flushPendingConstraintsEvents();
288 if (destination
== Plasma::LeftEdge
||
289 destination
== Plasma::RightEdge
) {
290 panel
->setMinimumSize(10, 35);
291 panel
->setMaximumSize(35,Kephal::ScreenUtils::screenSize(screen()).height());
292 panel
->resize(QSize(35, Kephal::ScreenUtils::screenSize(screen()).height()));
297 void Panel::updateBorders(const QRect
&geom
)
299 Plasma::Location loc
= location();
300 FrameSvg::EnabledBorders enabledBorders
= FrameSvg::AllBorders
;
303 //kDebug() << loc << s << formFactor() << geometry();
305 qreal topHeight
= m_background
->marginSize(Plasma::TopMargin
);
306 qreal bottomHeight
= m_background
->marginSize(Plasma::BottomMargin
);
307 qreal leftWidth
= m_background
->marginSize(Plasma::LeftMargin
);
308 qreal rightWidth
= m_background
->marginSize(Plasma::RightMargin
);
310 //remove unwanted borders
312 // do nothing in this case, we want all the borders
313 } else if (loc
== BottomEdge
|| loc
== TopEdge
) {
314 QRect r
= Kephal::ScreenUtils::screenGeometry(s
);
316 if (loc
== BottomEdge
) {
317 enabledBorders
^= FrameSvg::BottomBorder
;
320 enabledBorders
^= FrameSvg::TopBorder
;
324 if (geom
.x() <= r
.x()) {
325 enabledBorders
^= FrameSvg::LeftBorder
;
328 if (geom
.right() >= r
.right()) {
329 enabledBorders
^= FrameSvg::RightBorder
;
333 //kDebug() << "top/bottom: Width:" << width << ", height:" << height;
334 } else if (loc
== LeftEdge
|| loc
== RightEdge
) {
335 QRect r
= Kephal::ScreenUtils::screenGeometry(s
);
337 if (loc
== RightEdge
) {
338 enabledBorders
^= FrameSvg::RightBorder
;
341 enabledBorders
^= FrameSvg::LeftBorder
;
344 if (geom
.y() <= r
.y()) {
345 enabledBorders
^= FrameSvg::TopBorder
;
348 if (geom
.bottom() >= r
.bottom()) {
349 enabledBorders
^= FrameSvg::BottomBorder
;
353 //kDebug() << "left/right: Width:" << width << ", height:" << height;
355 kDebug() << "no location!?";
358 //activate borders and fetch sizes again
359 m_background
->setEnabledBorders(enabledBorders
);
360 m_background
->getMargins(leftWidth
, topHeight
, rightWidth
, bottomHeight
);
362 //calculation of extra margins has to be done after getMargins
363 const QGraphicsItem
*box
= toolBoxItem();
364 if (box
&& immutability() == Mutable
) {
365 QSizeF s
= box
->boundingRect().size();
366 if (formFactor() == Vertical
) {
367 //hardcoded extra margin for the toolbox right now
368 bottomHeight
+= s
.height();;
369 //Default to horizontal for now
371 if (QApplication::layoutDirection() == Qt::RightToLeft
) {
372 leftWidth
+= s
.width();
374 rightWidth
+= s
.width();
379 //invalidate the layout and set again
381 switch (location()) {
383 rightWidth
= qMin(rightWidth
, qMax(qreal(1), size().width() - KIconLoader::SizeMedium
));
386 leftWidth
= qMin(leftWidth
, qMax(qreal(1), size().width() - KIconLoader::SizeMedium
));
389 bottomHeight
= qMin(bottomHeight
, qMax(qreal(1), size().height() - KIconLoader::SizeMedium
));
392 topHeight
= qMin(topHeight
, qMax(qreal(1), size().height() - KIconLoader::SizeMedium
));
397 layout()->setContentsMargins(leftWidth
, topHeight
, rightWidth
, bottomHeight
);
398 layout()->invalidate();
404 void Panel::constraintsEvent(Plasma::Constraints constraints
)
406 kDebug() << "constraints updated with" << constraints
<< "!!!!!!";
410 if (constraints
& Plasma::FormFactorConstraint
) {
411 Plasma::FormFactor form
= formFactor();
412 Qt::Orientation layoutDirection
= form
== Plasma::Vertical
? Qt::Vertical
: Qt::Horizontal
;
413 // create our layout!
415 QGraphicsLayout
*lay
= layout();
416 QGraphicsLinearLayout
* linearLay
= dynamic_cast<QGraphicsLinearLayout
*>(lay
);
418 linearLay
->setOrientation(layoutDirection
);
420 linearLay
->setMaximumSize(size());
422 QGraphicsLinearLayout
*lay
= new QGraphicsLinearLayout(this);
423 lay
->setOrientation(layoutDirection
);
424 lay
->setContentsMargins(0, 0, 0, 0);
426 lay
->setSizePolicy(QSizePolicy(QSizePolicy::Expanding
,QSizePolicy::Expanding
));
428 updateBorders(geometry().toRect());
430 foreach (Applet
*applet
, applets()) {
431 lay
->addItem(applet
);
434 lay
->setMaximumSize(size());
438 //we need to know if the width or height is 100%
439 if (constraints
& Plasma::LocationConstraint
|| constraints
& Plasma::SizeConstraint
) {
440 m_currentSize
= geometry().size().toSize();
441 QRectF screenRect
= screen() >= 0 ? Kephal::ScreenUtils::screenGeometry(screen()) :
444 if ((formFactor() == Horizontal
&& m_currentSize
.width() >= screenRect
.width()) ||
445 (formFactor() == Vertical
&& m_currentSize
.height() >= screenRect
.height())) {
446 m_background
->setElementPrefix(location());
448 switch (location()) {
450 //this call will automatically fallback at no prefix if the element isn't available
451 m_background
->setElementPrefix("west-mini");
454 m_background
->setElementPrefix("east-mini");
457 m_background
->setElementPrefix("north-mini");
461 m_background
->setElementPrefix("south-mini");
466 m_background
->resizeFrame(m_currentSize
);
469 //FIXME: this seems the only way to correctly resize the layout the first time when the
470 // saved panel size is less than the default is to setting a maximum size.
471 // this shouldn't happen. maybe even a qgraphicslayout bug?
472 if (layout() && (constraints
& Plasma::SizeConstraint
)) {
473 layout()->setMaximumSize(size());
476 if (constraints
& Plasma::LocationConstraint
) {
477 setFormFactorFromLocation(location());
480 if (constraints
& Plasma::ImmutableConstraint
) {
481 bool unlocked
= immutability() == Plasma::Mutable
;
483 if (m_addPanelAction
) {
484 m_addPanelAction
->setEnabled(unlocked
);
485 m_addPanelAction
->setVisible(unlocked
);
488 if (m_configureAction
) {
489 m_configureAction
->setEnabled(unlocked
);
490 m_configureAction
->setVisible(unlocked
);
493 QGraphicsView
*panelView
= view();
495 updateBorders(panelView
->geometry());
500 void Panel::saveState(KConfigGroup
&config
) const
502 config
.writeEntry("minimumSize", minimumSize());
503 config
.writeEntry("maximumSize", maximumSize());
506 void Panel::themeUpdated()
508 //if the theme is changed all the calculations needs to be done again
509 //and resize based on the change in the theme bordersize
517 qreal oldBottomHeight
;
518 qreal newBottomHeight
;
520 layout()->getContentsMargins(&oldLeftWidth
, &oldTopHeight
, &oldRightWidth
, &oldBottomHeight
);
521 m_background
->getMargins(newLeftWidth
, newTopHeight
, newRightWidth
, newBottomHeight
);
523 QSize
newSize(size().width()-(oldLeftWidth
- newLeftWidth
)-(oldRightWidth
- newRightWidth
),
524 size().height()-(oldTopHeight
- newTopHeight
)-(oldBottomHeight
- newBottomHeight
));
528 if (formFactor() == Plasma::Vertical
) {
529 setMaximumWidth(newSize
.width());
530 setMinimumWidth(newSize
.width());
532 setMaximumHeight(newSize
.height());
533 setMinimumHeight(newSize
.height());
536 updateBorders(geometry().toRect());
539 void Panel::paintInterface(QPainter
*painter
,
540 const QStyleOptionGraphicsItem
*option
,
541 const QRect
& contentsRect
)
543 Q_UNUSED(contentsRect
)
544 //FIXME: this background drawing is bad and ugly =)
545 // draw the background untransformed (saves lots of per-pixel-math)
547 painter
->resetTransform();
549 const Containment::StyleOption
*containmentOpt
= qstyleoption_cast
<const Containment::StyleOption
*>(option
);
552 if (containmentOpt
&& containmentOpt
->view
) {
553 viewGeom
= containmentOpt
->view
->geometry();
556 if (m_maskDirty
|| m_lastViewGeom
!= viewGeom
) {
558 m_lastViewGeom
= viewGeom
;
560 updateBorders(viewGeom
);
561 if (containmentOpt
&& containmentOpt
->view
) {
562 containmentOpt
->view
->setMask(m_background
->mask());
566 // blit the background (saves all the per-pixel-products that blending does)
567 painter
->setCompositionMode(QPainter::CompositionMode_Source
);
568 painter
->setRenderHint(QPainter::Antialiasing
);
570 m_background
->paintFrame(painter
, option
->exposedRect
);
572 // restore transformation and composition mode
576 void Panel::setFormFactorFromLocation(Plasma::Location loc
) {
580 //kDebug() << "setting horizontal form factor";
581 setFormFactor(Plasma::Horizontal
);
585 //kDebug() << "setting vertical form factor";
586 setFormFactor(Plasma::Vertical
);
589 //TODO: implement a form factor for floating panels
590 kDebug() << "Floating is unimplemented.";
593 kDebug() << "invalid location!!";
597 void Panel::showDropZone(const QPoint pos
)
603 // if the drop isn't happening on the outer edges and is instead
604 // actually poised over an applet, ignore it
605 if (((formFactor() == Plasma::Vertical
&& pos
.y() > 1 && pos
.y() > size().height() - 2) ||
606 (pos
.x() > 1 && pos
.x() < size().width() - 2)) &&
607 scene()->itemAt(mapToScene(pos
)) != this) {
611 QGraphicsLinearLayout
*lay
= dynamic_cast<QGraphicsLinearLayout
*>(layout());
617 if (pos
== QPoint()) {
619 lay
->removeItem(m_spacer
);
625 //lucky case: the spacer is already in the right position
626 if (m_spacer
&& m_spacer
->geometry().contains(pos
)) {
630 Plasma::FormFactor f
= formFactor();
631 int insertIndex
= -1;
633 //FIXME: needed in two places, make it a function?
634 for (int i
= 0; i
< lay
->count(); ++i
) {
635 QRectF siblingGeometry
= lay
->itemAt(i
)->geometry();
637 if (f
== Plasma::Horizontal
) {
638 qreal middle
= (siblingGeometry
.left() + siblingGeometry
.right()) / 2.0;
639 if (pos
.x() < middle
) {
642 } else if (pos
.x() <= siblingGeometry
.right()) {
646 } else { // Plasma::Vertical
647 qreal middle
= (siblingGeometry
.top() + siblingGeometry
.bottom()) / 2.0;
648 if (pos
.y() < middle
) {
651 } else if (pos
.y() <= siblingGeometry
.bottom()) {
658 m_spacerIndex
= insertIndex
;
659 if (insertIndex
!= -1) {
661 m_spacer
= new Spacer(this);
662 m_spacer
->panel
= this;
664 lay
->removeItem(m_spacer
);
666 lay
->insertItem(insertIndex
, m_spacer
);
671 K_EXPORT_PLASMA_APPLET(panel
, Panel
)