plasma-framework/extenderitem.cpp

1147 lines
36 KiB
C++
Raw Normal View History

/*
* Copyright 2008 by Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "extenderitem.h"
#include <QApplication>
#include <QAction>
#include <QBitmap>
#include <QGraphicsSceneResizeEvent>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsLinearLayout>
#include <QLayout>
#include <QPainter>
#include <QTimer>
#include <kdebug.h>
#include <kicon.h>
#include <kwindowsystem.h>
#include <kglobalsettings.h>
#include "applet.h"
#include "containment.h"
#include "corona.h"
#include "dialog.h"
#include "extender.h"
#include "framesvg.h"
#include "popupapplet.h"
#include "theme.h"
#include "view.h"
#include "private/applet_p.h"
#include "private/extender_p.h"
#include "private/extenderitem_p.h"
#include "widgets/iconwidget.h"
namespace Plasma
{
class ExtenderItemView : public QGraphicsView
{
public:
ExtenderItemView(QGraphicsScene *scene, QWidget *parent = 0)
: QGraphicsView(scene, parent)
{
//since this view witll have a really short lifespan it can be checked a single time
composite = KWindowSystem::compositingActive();
}
~ExtenderItemView()
{}
void drawBackground(QPainter *painter, const QRectF &rect)
{
if (composite) {
painter->setCompositionMode(QPainter::CompositionMode_Source);
painter->fillRect(rect.toAlignedRect(), Qt::transparent);
} else {
QGraphicsView::drawBackground(painter, rect);
}
}
private:
bool composite;
};
ExtenderItem::ExtenderItem(Extender *hostExtender, uint extenderItemId)
: QGraphicsWidget(hostExtender),
d(new ExtenderItemPrivate(this, hostExtender))
{
Q_ASSERT(hostExtender);
//set the extenderId
if (extenderItemId) {
d->extenderItemId = extenderItemId;
ExtenderItemPrivate::s_maxExtenderItemId =
qMax(ExtenderItemPrivate::s_maxExtenderItemId, extenderItemId);
} else {
d->extenderItemId = ++ExtenderItemPrivate::s_maxExtenderItemId;
}
//create the toolbox.
d->toolbox = new QGraphicsWidget(this);
d->toolboxLayout = new QGraphicsLinearLayout(d->toolbox);
d->toolbox->setLayout(d->toolboxLayout);
//create items's configgroup
KConfigGroup cg = hostExtender->d->applet->config("ExtenderItems");
KConfigGroup dg = KConfigGroup(&cg, QString::number(d->extenderItemId));
uint sourceAppletId = dg.readEntry("sourceAppletId", 0);
//check if we're creating a new item or reinstantiating an existing one.
if (!sourceAppletId) {
//The item is new
dg.writeEntry("sourceAppletPluginName", hostExtender->d->applet->pluginName());
dg.writeEntry("sourceAppletId", hostExtender->d->applet->id());
dg.writeEntry("extenderIconName", hostExtender->d->applet->icon());
d->sourceApplet = hostExtender->d->applet;
d->collapseIcon = new IconWidget(KIcon(hostExtender->d->applet->icon()), "", this);
} else {
//The item already exists.
d->name = dg.readEntry("extenderItemName", "");
d->title = dg.readEntry("extenderTitle", "");
setCollapsed(dg.readEntry("isCollapsed", false));
QString iconName = dg.readEntry("extenderIconName", "utilities-desktop-extra");
if (iconName.isEmpty()) {
iconName = "utilities-desktop-extra";
}
d->collapseIcon = new IconWidget(KIcon(iconName), "", this);
//Find the sourceapplet.
Corona *corona = hostExtender->d->applet->containment()->corona();
foreach (Containment *containment, corona->containments()) {
foreach (Applet *applet, containment->applets()) {
if (applet->id() == sourceAppletId &&
applet->pluginName() == dg.readEntry("sourceAppletPluginName", "")) {
d->sourceApplet = applet;
}
}
}
}
//make sure we keep monitoring if the source applet still exists, so the return to source icon
//can be hidden if it is removed.
connect(d->sourceApplet, SIGNAL(destroyed()), this, SLOT(sourceAppletRemoved()));
connect(d->collapseIcon, SIGNAL(clicked()), this, SLOT(toggleCollapse()));
//set the extender we want to move to.
setExtender(hostExtender);
//show or hide the return to source icon.
d->updateToolBox();
//set the image paths, image sizes and collapseIcon position.
d->themeChanged();
setAcceptsHoverEvents(true);
connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged()));
}
ExtenderItem::~ExtenderItem()
{
//make sure the original mousepointer always get's restored.
if (d->mouseOver) {
QApplication::restoreOverrideCursor();
}
delete d;
}
KConfigGroup ExtenderItem::config() const
{
KConfigGroup cg = d->extender->d->applet->config("ExtenderItems");
return KConfigGroup(&cg, QString::number(d->extenderItemId));
}
void ExtenderItem::setTitle(const QString &title)
{
d->title = title;
config().writeEntry("extenderTitle", title);
update();
}
QString ExtenderItem::title() const
{
return d->title;
}
void ExtenderItem::setName(const QString &name)
{
d->name = name;
config().writeEntry("extenderItemName", name);
}
QString ExtenderItem::name() const
{
return d->name;
}
void ExtenderItem::setWidget(QGraphicsItem *widget)
{
widget->setParentItem(this);
QSizeF panelSize(QSizeF(size().width() - d->bgLeft - d->bgRight,
d->iconSize() + d->dragTop + d->dragBottom));
widget->setPos(QPointF(d->bgLeft + d->dragLeft, panelSize.height() +
d->bgTop + d->dragTop));
d->widget = widget;
setCollapsed(isCollapsed()); //updates the size hints.
}
QGraphicsItem *ExtenderItem::widget() const
{
return d->widget;
}
void ExtenderItem::setIcon(const QIcon &icon)
{
d->iconName.clear();
d->collapseIcon->setIcon(icon);
}
void ExtenderItem::setIcon(const QString &icon)
{
if (icon != d->iconName) {
d->collapseIcon->setIcon(icon);
d->iconName = icon;
config().writeEntry("extenderIconName", icon);
}
}
QIcon ExtenderItem::icon() const
{
return d->collapseIcon->icon();
}
void ExtenderItem::setExtender(Extender *extender, const QPointF &pos)
{
Q_ASSERT(extender);
if (extender == d->extender) {
//We're not moving between extenders, so just insert this item back into the layout.
setParentItem(extender);
extender->d->addExtenderItem(this, pos);
return;
}
//We are switching extender...
//first remove this item from the old extender.
d->extender->d->removeExtenderItem(this);
emit d->extender->itemDetached(this);
//move the configuration.
if (d->hostApplet() && (extender != d->extender)) {
KConfigGroup c = extender->d->applet->config("ExtenderItems");
config().reparent(&c);
}
d->extender = extender;
//change parent.
setParentItem(extender);
extender->d->addExtenderItem(this, pos);
//cancel the timer.
if (d->expirationTimer && isDetached()) {
d->expirationTimer->stop();
delete d->expirationTimer;
d->expirationTimer = 0;
}
//set the background svg to that of the extender we're moving to.
d->themeChanged();
//we might have to enable or disable the returnToSource button.
d->updateToolBox();
}
Extender *ExtenderItem::extender() const
{
return d->extender;
}
bool ExtenderItem::isCollapsed() const
{
return d->collapsed;
}
void ExtenderItem::setAutoExpireDelay(uint time)
{
if (!time) {
if (d->expirationTimer) {
d->expirationTimer->stop();
delete d->expirationTimer;
d->expirationTimer = 0;
}
return;
}
if (!isDetached()) {
if (!d->expirationTimer) {
d->expirationTimer = new QTimer(this);
connect(d->expirationTimer, SIGNAL(timeout()), this, SLOT(destroy()));
}
d->expirationTimer->stop();
d->expirationTimer->setSingleShot(true);
d->expirationTimer->setInterval(time);
d->expirationTimer->start();
}
}
uint ExtenderItem::autoExpireDelay() const
{
if (d->expirationTimer) {
return d->expirationTimer->interval();
} else {
return 0;
}
}
bool ExtenderItem::isDetached() const
{
if (d->hostApplet()) {
return (d->sourceApplet != d->hostApplet());
} else {
return false;
}
}
void ExtenderItem::addAction(const QString &name, QAction *action)
{
Q_ASSERT(action);
if (d->actionsInOrder.contains(action)) {
return;
}
d->actions.insert(name, action);
d->actionsInOrder.append(action);
connect(action, SIGNAL(changed()), this, SLOT(updateToolBox()));
connect(action, SIGNAL(destroyed(QObject*)), this, SLOT(actionDestroyed(QObject*)));
d->updateToolBox();
}
QAction *ExtenderItem::action(const QString &name) const
{
return d->actions.value(name, 0);
}
void ExtenderItem::showCloseButton()
{
if (d->destroyActionVisibility) {
return;
}
d->destroyActionVisibility = true;
d->updateToolBox();
}
void ExtenderItem::hideCloseButton()
{
if (!d->destroyActionVisibility) {
return;
}
d->destroyActionVisibility = false;
d->updateToolBox();
}
void ExtenderItem::destroy()
{
if (d->mousePressed) {
//avoid being destroyed while we're being dragged.
return;
}
d->hostApplet()->config("ExtenderItems").deleteGroup(QString::number(d->extenderItemId));
d->extender->d->removeExtenderItem(this);
deleteLater();
}
void ExtenderItem::setCollapsed(bool collapsed)
{
config().writeEntry("isCollapsed", collapsed);
d->collapsed = collapsed;
qreal marginWidth = d->bgLeft + d->bgRight + d->dragLeft + d->dragRight;
qreal marginHeight = d->bgTop + d->bgBottom + 2 * d->dragTop + 2 * d->dragBottom;
if (!d->widget) {
setPreferredSize(QSizeF(200, d->dragHandleRect().height()));
setMinimumSize(QSizeF(0, d->dragHandleRect().height()));
//FIXME: wasn't there some sort of QWIDGETMAXSIZE thingy?
setMaximumSize(QSizeF(1000, d->dragHandleRect().height()));
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
updateGeometry();
return;
}
d->widget->setVisible(!collapsed);
QSizeF min;
QSizeF pref;
QSizeF max;
if (d->widget->isWidget()) {
QGraphicsWidget *graphicsWidget = static_cast<QGraphicsWidget*>(d->widget);
min = graphicsWidget->minimumSize();
pref = graphicsWidget->preferredSize();
max = graphicsWidget->maximumSize();
} else {
min = d->widget->boundingRect().size();
pref = d->widget->boundingRect().size();
max = d->widget->boundingRect().size();
}
if (collapsed) {
setPreferredSize(QSizeF(pref.width() + marginWidth,
d->dragHandleRect().height() + marginHeight));
setMinimumSize(QSizeF(min.width() + marginWidth,
d->dragHandleRect().height() + marginHeight));
setMaximumSize(QSizeF(max.width() + marginWidth,
d->dragHandleRect().height() + marginHeight));
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
if (d->collapseIcon) {
d->collapseIcon->setToolTip(i18n("Expand this widget"));
}
} else {
setPreferredSize(QSizeF(pref.width() + marginWidth,
pref.height() + d->dragHandleRect().height() + marginHeight));
setMinimumSize(QSizeF(min.width() + marginWidth,
min.height() + d->dragHandleRect().height() + marginHeight));
setMaximumSize(QSizeF(max.width() + marginWidth,
max.height() + d->dragHandleRect().height() + marginHeight));
if (d->widget->isWidget()) {
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
} else {
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
if (d->collapseIcon) {
d->collapseIcon->setToolTip(i18n("Collapse this widget"));
}
}
updateGeometry();
d->extender->d->adjustSizeHints();
}
void ExtenderItem::returnToSource()
{
if (!d || !d->sourceApplet) {
return;
}
if (d->sourceApplet->d) {
setExtender(d->sourceApplet->d->extender);
}
}
void ExtenderItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setRenderHint(QPainter::Antialiasing, true);
if (d->background->enabledBorders() != (FrameSvg::LeftBorder | FrameSvg::RightBorder) &&
d->background->enabledBorders()) {
//Don't paint if only the left and right borders are enabled, we only use the left and right
//border in this situation to set the correct margins on this item.
d->background->paintFrame(painter, QPointF(0, 0));
}
d->dragger->paintFrame(painter, QPointF(d->bgLeft, d->bgTop));
//We don't need to paint a title if there is no title.
if (d->title.isEmpty()) {
return;
}
//set the font for the title.
Plasma::Theme *theme = Plasma::Theme::defaultTheme();
QFont font = theme->font(Plasma::Theme::DefaultFont);
font.setWeight(QFont::Bold);
//create a pixmap with the title that is faded out at the right side of the titleRect.
QRectF rect = QRectF(d->titleRect().width() - 30, 0, 30, d->titleRect().height());
QPixmap pixmap(d->titleRect().size().toSize());
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
p.setPen(theme->color(Plasma::Theme::TextColor));
p.setFont(font);
p.drawText(QRectF(QPointF(0, 0), d->titleRect().size()),
Qt::TextSingleLine | Qt::AlignVCenter | Qt::AlignLeft,
d->title);
// Create the alpha gradient for the fade out effect
QLinearGradient alphaGradient(0, 0, 1, 0);
alphaGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
//TODO: correct handling of right to left text.
alphaGradient.setColorAt(0, QColor(0, 0, 0, 255));
alphaGradient.setColorAt(1, QColor(0, 0, 0, 0));
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(rect, alphaGradient);
p.end();
painter->drawPixmap(d->titleRect().topLeft(), pixmap);
}
void ExtenderItem::moveEvent(QGraphicsSceneMoveEvent *event)
{
Q_UNUSED(event);
if (d->toplevel) {
d->toplevel->setSceneRect(sceneBoundingRect());
d->toplevel->setMask(d->background->mask());
}
}
void ExtenderItem::resizeEvent(QGraphicsSceneResizeEvent *event)
{
d->resizeContent(event->newSize());
if (d->toplevel) {
d->toplevel->setSceneRect(sceneBoundingRect());
d->toplevel->setMask(d->background->mask());
}
}
void ExtenderItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (!(d->dragHandleRect().contains(event->pos())) ||
d->extender->d->applet->immutability() != Plasma::Mutable) {
event->ignore();
return;
}
QApplication::setOverrideCursor(Qt::ClosedHandCursor);
d->mousePos = event->pos().toPoint();
d->deltaScene = pos();
d->mousePressed = true;
}
void ExtenderItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (d->mousePressed && !d->dragStarted &&
(d->mousePos - event->pos().toPoint()).manhattanLength() >= QApplication::startDragDistance()) {
//start the drag:
d->dragStarted = true;
//set the zValue to the maximum.
d->hostApplet()->raise();
setZValue(d->hostApplet()->zValue());
QPointF mousePos = d->scenePosFromScreenPos(event->screenPos());
if (!mousePos.isNull()) {
d->extender->itemHoverMoveEvent(this, d->extender->mapFromScene(mousePos));
}
d->extender->d->removeExtenderItem(this);
d->themeChanged();
} else if (!d->dragStarted) {
return;
}
//keep track of the movement in scene coordinates. we use this to set the position of the
//applet when remaining in the current view.
d->deltaScene += event->scenePos() - event->lastScenePos();
//set a rect in screencoordinates so we can check when we need to move to a toplevel
//view.
QRect screenRect = QRect();
screenRect.setTopLeft(event->screenPos() - d->mousePos);
screenRect.setSize(d->screenRect().size());
Corona *corona = d->hostApplet()->containment()->corona();
if (d->leaveCurrentView(screenRect)) {
//we're moving the applet to a toplevel view, so place it somewhere out of sight
//first: in the topleft quadrant.
if (!d->toplevel) {
//FIXME: duplication from applethandle
//create a toplevel view and aim it at the applet.
corona->addOffscreenWidget(this);
d->toplevel = new ExtenderItemView(scene(), 0);
d->toplevel->setWindowFlags(
Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
d->toplevel->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
d->toplevel->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
d->toplevel->setFrameShape(QFrame::NoFrame);
d->toplevel->setSceneRect(sceneBoundingRect());
d->toplevel->setGeometry(screenRect);
d->toplevel->setMask(d->background->mask());
d->toplevel->show();
}
d->toplevel->setSceneRect(sceneBoundingRect());
d->toplevel->setGeometry(screenRect);
d->toplevel->setMask(d->background->mask());
} else {
corona->removeOffscreenWidget(this);
setParentItem(d->hostApplet());
setPos(d->deltaScene);
//remove the toplevel view.
delete d->toplevel;
d->toplevel = 0;
}
//let's insert spacers in applets we're hovering over for some useful visual feedback.
//check which view we're hovering over and use that information to get the mouse
//position in scene coordinates (event->scenePos won't work, since it doesn't take in
//consideration that you're leaving the current view).
QPointF mousePos = d->scenePosFromScreenPos(event->screenPos());
//find the extender we're hovering over.
Extender *targetExtender = 0;
if (!mousePos.isNull()) {
foreach (Containment *containment, corona->containments()) {
foreach (Applet *applet, containment->applets()) {
if (applet->d->extender &&
(applet->sceneBoundingRect().contains(mousePos) ||
applet->d->extender->sceneBoundingRect().contains(mousePos))) {
targetExtender = applet->d->extender;
//check if we're hovering over an popupapplet, and open it up in case it does.
PopupApplet *popupApplet = qobject_cast<PopupApplet*>(applet);
if (popupApplet && (applet->formFactor() == Plasma::Horizontal ||
applet->formFactor() == Plasma::Vertical)) {
popupApplet->showPopup();
}
}
}
if (containment->sceneBoundingRect().contains(mousePos) && !targetExtender) {
containment->showDropZone(containment->mapFromScene(mousePos).toPoint());
} else {
containment->showDropZone(QPoint());
}
}
}
//remove any previous spacers.
if (targetExtender != d->previousTargetExtender) {
if (d->previousTargetExtender) {
d->previousTargetExtender->itemHoverLeaveEvent(this);
d->previousTargetExtender->disconnect(this, SIGNAL(destroyed(QObject*)));
}
d->previousTargetExtender = targetExtender;
//monitor the current previousTargetExtender, so we don't accidently try to remove it's
//spacer after the extender has been destoryed.
if (targetExtender) {
connect(targetExtender, SIGNAL(destroyed(QObject*)),
this, SLOT(previousTargetExtenderDestroyed(QObject*)));
}
if (targetExtender) {
targetExtender->itemHoverEnterEvent(this);
}
}
//insert a spacer if the applet accepts detachables.
if (targetExtender) {
if (targetExtender->sceneBoundingRect().contains(mousePos)) {
targetExtender->itemHoverMoveEvent(this, targetExtender->mapFromScene(mousePos));
}
}
}
void ExtenderItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if (d->titleRect().contains(event->pos())) {
d->toggleCollapse();
}
}
void ExtenderItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
if (d->titleRect().contains(event->pos()) &&
d->extender->d->applet->immutability() == Plasma::Mutable) {
if (!d->mouseOver) {
QApplication::setOverrideCursor(Qt::OpenHandCursor);
d->mouseOver = true;
}
} else {
if (d->mouseOver) {
QApplication::restoreOverrideCursor();
d->mouseOver = false;
}
}
}
void ExtenderItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
Q_UNUSED(event);
if (d->mouseOver) {
QApplication::restoreOverrideCursor();
d->mouseOver = false;
}
}
void ExtenderItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (d->mousePressed) {
QApplication::restoreOverrideCursor();
}
d->mousePressed = false;
if (d->dragStarted) {
d->dragStarted = false;
d->previousTargetExtender = 0;
//remove the toplevel view
if (d->toplevel) {
delete d->toplevel;
d->toplevel = 0;
}
//let's insert spacers in applets we're hovering over for some useful visual feedback.
//check which view we're hovering over and use that information to get the mouse
//position in scene coordinates (event->scenePos won't work, since it doesn't take in
//consideration that you're leaving the current view).
QPointF mousePos = d->scenePosFromScreenPos(event->screenPos());
//find the extender we're hovering over.
Extender *targetExtender = 0;
Corona *corona = qobject_cast<Corona*>(d->extender->d->applet->scene());
corona->removeOffscreenWidget(this);
if (!mousePos.isNull()) {
foreach (Containment *containment, corona->containments()) {
containment->showDropZone(QPoint());
foreach (Applet *applet, containment->applets()) {
if (applet->d->extender &&
(applet->sceneBoundingRect().contains(mousePos) ||
applet->d->extender->sceneBoundingRect().contains(mousePos))) {
targetExtender = applet->d->extender;
}
}
}
}
//are we hovering over an applet that accepts extender items?
if (targetExtender) {
if (targetExtender->sceneBoundingRect().contains(mousePos)) {
setExtender(targetExtender, targetExtender->mapFromScene(mousePos));
} else {
setExtender(targetExtender);
}
} else {
//apparently, it is not, so instantiate a new ExtenderApplet.
bool extenderCreated = false;
mousePos = d->scenePosFromScreenPos(event->screenPos());
if (!mousePos.isNull()) {
foreach (Containment *containment, corona->containments()) {
if (containment->sceneBoundingRect().contains(mousePos)) {
kDebug() << "Instantiate a new ExtenderApplet";
//when on a desktopcontainment, we use the widgets topleft corner to
//position the applet in the containment, on other containments, we use the
//actual mouse position.
QPointF position;
if (containment->type() == Plasma::Containment::DesktopContainment) {
position = containment->mapFromScene(
d->scenePosFromScreenPos(event->screenPos() - d->mousePos));
} else {
position = containment->mapFromScene(mousePos);
}
Applet *applet = containment->addApplet("internal:extender",
QVariantList(),
QRectF(containment->mapFromScene(mousePos), size()));
if (applet) {
setExtender(applet->d->extender);
QSizeF margin = applet->size() - applet->contentsRect().size();
applet->setMinimumSize(minimumSize() + margin);
applet->setPreferredSize(preferredSize() + margin);
applet->resize(preferredSize());
extenderCreated = true;
} else {
kDebug() << "Creating internal:extender applet failed, probably "
<< "because widgets are locked.";
}
}
}
}
//if no containment is at the position where the item is dropped, just move the item
//back to where it came from.
if (!extenderCreated) {
setExtender(extender());
kDebug() << "Move the item back to where it came from.";
}
}
}
}
ExtenderItemPrivate::ExtenderItemPrivate(ExtenderItem *extenderItem, Extender *hostExtender)
: q(extenderItem),
widget(0),
toolbox(0),
toplevel(0),
previousTargetExtender(0),
extender(hostExtender),
sourceApplet(0),
dragger(new FrameSvg(extenderItem)),
background(new FrameSvg(extenderItem)),
collapseIcon(0),
title(QString()),
mousePressed(false),
mouseOver(false),
dragStarted(false),
destroyActionVisibility(false),
collapsed(false),
expirationTimer(0)
{
dragLeft = dragTop = dragRight = dragBottom = 0;
bgLeft = bgTop = bgRight = bgBottom = 0;
}
ExtenderItemPrivate::~ExtenderItemPrivate()
{
delete widget;
widget = 0;
delete toplevel;
}
//returns a Rect containing the area of the detachable where the draghandle will be drawn.
QRectF ExtenderItemPrivate::dragHandleRect()
{
QSizeF panelSize(QSizeF(q->size().width() - bgLeft - bgRight,
iconSize() + dragTop + dragBottom));
return QRectF(QPointF(bgLeft, bgTop), panelSize);
}
QRectF ExtenderItemPrivate::titleRect()
{
return dragHandleRect().adjusted(dragLeft + collapseIcon->size().width() + 1, dragTop,
-toolbox->size().width() - dragRight, -dragBottom);
}
bool ExtenderItemPrivate::leaveCurrentView(const QRect &rect)
{
if ((q->sceneBoundingRect().left() < 0)) {
//always leaveCurrentView when the item is in a Plasma::Dialog which can easy be
//seen by checking if it is in topleft quadrant and it's not just there because it's
//being viewed by the toplevel view.
return true;
}
foreach (QWidget *widget, QApplication::topLevelWidgets()) {
if (widget->geometry().intersects(rect) && widget->isVisible() && widget != toplevel) {
//is this widget a plasma view, a different view then our current one,
//AND not a dashboardview?
QGraphicsView *v = qobject_cast<QGraphicsView*>(widget);
QGraphicsView *currentV = 0;
if (hostApplet()) {
currentV = qobject_cast<QGraphicsView*>(hostApplet()->containment()->view());
}
if (v && v != currentV) {
return true;
}
}
}
return false;
}
QRect ExtenderItemPrivate::screenRect()
{
return hostApplet()->containment()->view()
->mapFromScene(q->sceneBoundingRect()).boundingRect();
}
void ExtenderItemPrivate::toggleCollapse()
{
q->setCollapsed(!q->isCollapsed());
}
void ExtenderItemPrivate::updateToolBox()
{
Q_ASSERT(toolbox);
Q_ASSERT(dragger);
Q_ASSERT(toolboxLayout);
uint iconHeight = iconSize();
//TODO: only delete items that actually have to be deleted, current performance is horrible.
while (toolboxLayout->count()) {
QGraphicsLayoutItem *icon = toolboxLayout->itemAt(0);
QGraphicsWidget *widget = dynamic_cast<QGraphicsWidget*>(icon);
widget->deleteLater();
toolboxLayout->removeAt(0);
}
//add the actions that are actually set to visible.
foreach (QAction *action, actionsInOrder) {
if (action->isVisible()) {
IconWidget *icon = new IconWidget(q);
icon->setAction(action);
QSizeF iconSize = icon->sizeFromIconSize(iconHeight);
icon->setMinimumSize(iconSize);
icon->setMaximumSize(iconSize);
toolboxLayout->addItem(icon);
}
}
//add the returntosource icon if we are detached, and have a source applet.
if (q->isDetached() && sourceApplet) {
IconWidget *returnToSource = new IconWidget(q);
returnToSource->setSvg("widgets/configuration-icons", "return-to-source");
QSizeF iconSize = returnToSource->sizeFromIconSize(iconHeight);
returnToSource->setMinimumSize(iconSize);
returnToSource->setMaximumSize(iconSize);
toolboxLayout->addItem(returnToSource);
QObject::connect(returnToSource, SIGNAL(clicked()), q, SLOT(returnToSource()));
}
//add the close icon if desired.
if (destroyActionVisibility) {
IconWidget *destroyAction = new IconWidget(q);
destroyAction->setSvg("widgets/configuration-icons", "close");
QSizeF iconSize = destroyAction->sizeFromIconSize(iconHeight);
destroyAction->setMinimumSize(iconSize);
destroyAction->setMaximumSize(iconSize);
toolboxLayout->addItem(destroyAction);
QObject::connect(destroyAction, SIGNAL(clicked()), q, SLOT(destroy()));
}
toolboxLayout->updateGeometry();
//position the toolbox correctly.
QSizeF minimum = toolboxLayout->minimumSize();
toolbox->resize(minimum);
repositionToolbox();
}
void ExtenderItemPrivate::repositionToolbox()
{
QSizeF minimum = toolboxLayout->minimumSize();
toolbox->setPos(q->size().width() - minimum.width() - bgRight,
(dragHandleRect().height()/2) -
(minimum.height()/2) + bgTop);
}
//TODO: something like this as static function in corona might be a good idea.
QPointF ExtenderItemPrivate::scenePosFromScreenPos(const QPoint &pos) const
{
//get the stacking order of the toplevel windows and remove the toplevel view that's
//only here while dragging, since we're not interested in finding that.
QList<WId> order = KWindowSystem::stackingOrder();
if (toplevel) {
order.removeOne(toplevel->winId());
}
QGraphicsView *found = 0;
foreach (QWidget *w, QApplication::topLevelWidgets()) {
QGraphicsView *v = 0;
//first check if we're over a Dialog.
Dialog *dialog = qobject_cast<Dialog*>(w);
if (dialog) {
if (dialog->isVisible() && dialog->geometry().contains(pos)) {
v = qobject_cast<QGraphicsView*>(dialog->layout()->itemAt(0)->widget());
if (v) {
return v->mapToScene(v->mapFromGlobal(pos));
}
}
} else {
v = qobject_cast<QGraphicsView *>(w);
}
//else check if it is a QGV:
if (v && w->isVisible() && w->geometry().contains(pos)) {
if (found && order.contains(found->winId())) {
if (order.indexOf(found->winId()) < order.indexOf(v->winId())) {
found = v;
}
} else {
found = v;
}
}
}
if (!found) {
return QPointF();
}
return found->mapToScene(found->mapFromGlobal(pos));
}
Applet *ExtenderItemPrivate::hostApplet() const
{
if (extender) {
return extender->d->applet;
} else {
return 0;
}
}
void ExtenderItemPrivate::themeChanged()
{
background->setImagePath("widgets/extender-background");
if (mousePressed) {
background->setEnabledBorders(FrameSvg::AllBorders);
} else {
background->setEnabledBorders(extender->enabledBordersForItem(q));
}
background->getMargins(bgLeft, bgTop, bgRight, bgBottom);
dragger->setImagePath("widgets/extender-dragger");
dragger->getMargins(dragLeft, dragTop, dragRight, dragBottom);
QSizeF panelSize(QSizeF(q->size().width() - bgLeft - bgRight,
iconSize() + dragTop + dragBottom));
//resize the collapse icon.
collapseIcon->resize(collapseIcon->sizeFromIconSize(iconSize()));
//reposition the collapse icon based on the new margins and size.
collapseIcon->setPos(bgLeft + dragLeft,
panelSize.height()/2 -
collapseIcon->size().height()/2 + bgTop);
//reposition the widget based on the new margins.
if (widget) {
widget->setPos(QPointF(bgLeft + dragLeft, panelSize.height() +
bgTop + dragTop));
}
//reposition the toolbox.
repositionToolbox();
//setCollapsed recalculates size hints.
q->setCollapsed(q->isCollapsed());
if (!q->size().isEmpty())
resizeContent(q->size());
}
void ExtenderItemPrivate::sourceAppletRemoved()
{
//the original source applet is removed, set the pointer to 0 and no longer show the return to
//source icon.
sourceApplet = 0;
updateToolBox();
}
qreal ExtenderItemPrivate::iconSize()
{
//read the icon size hint, and enforce a minimum size of 16x16
QSizeF size = dragger->elementSize("hint-preferred-icon-size");
size = size.expandedTo(QSizeF(16,16));
//return the size of the text, if that is larger then the recommended icon size
Plasma::Theme *theme = Plasma::Theme::defaultTheme();
QFont font = theme->font(Plasma::Theme::DefaultFont);
QFontMetrics fm(font);
return qMax(size.height(), (qreal) fm.height());
}
void ExtenderItemPrivate::resizeContent(const QSizeF &newSize)
{
qreal width = newSize.width();
qreal height = newSize.height();
//resize the dragger
dragger->resizeFrame(QSizeF(width - bgLeft - bgRight,
iconSize() + dragTop + dragBottom));
//resize the applet background
background->resizeFrame(newSize);
//resize the widget
if (widget && widget->isWidget()) {
QSizeF newWidgetSize(width - bgLeft - bgRight - dragLeft - dragRight,
height - dragHandleRect().height() - bgTop - bgBottom -
2 * dragTop - 2 * dragBottom);
QGraphicsWidget *graphicsWidget = static_cast<QGraphicsWidget*>(widget);
graphicsWidget->resize(newWidgetSize);
}
//reposition the toolbox.
repositionToolbox();
q->update();
}
void ExtenderItemPrivate::previousTargetExtenderDestroyed(QObject *o)
{
Q_UNUSED(o)
previousTargetExtender = 0;
}
void ExtenderItemPrivate::actionDestroyed(QObject *o)
{
QAction *action = static_cast<QAction *>(o);
QMutableHashIterator<QString, QAction *> hit(actions);
while (hit.hasNext()) {
if (hit.next().value() == action) {
hit.remove();
break;
}
}
QMutableListIterator<QAction *> lit(actionsInOrder);
while (lit.hasNext()) {
if (lit.next() == action) {
lit.remove();
break;
}
}
}
uint ExtenderItemPrivate::s_maxExtenderItemId = 0;
} // namespace Plasma
#include "extenderitem.moc"