a757529178
svn path=/trunk/KDE/kdelibs/; revision=1212619
1072 lines
33 KiB
C++
1072 lines
33 KiB
C++
/*
|
|
* Copyright 2008, 2009 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 <QAction>
|
|
#include <QApplication>
|
|
#include <QBitmap>
|
|
#include <QDrag>
|
|
#include <QGraphicsSceneResizeEvent>
|
|
#include <QGraphicsSceneMouseEvent>
|
|
#include <QGraphicsLinearLayout>
|
|
#include <QLayout>
|
|
#include <QMimeData>
|
|
#include <QPainter>
|
|
#include <QTimer>
|
|
|
|
#include <kdebug.h>
|
|
#include <kicon.h>
|
|
#include <kiconloader.h>
|
|
#include <ksharedconfig.h>
|
|
|
|
#include "applet.h"
|
|
#include "containment.h"
|
|
#include "corona.h"
|
|
#include "dialog.h"
|
|
#include "extender.h"
|
|
#include "extendergroup.h"
|
|
#include "framesvg.h"
|
|
#include "popupapplet.h"
|
|
#include "theme.h"
|
|
#include "view.h"
|
|
|
|
#include "widgets/iconwidget.h"
|
|
#include "widgets/pushbutton.h"
|
|
|
|
#include "private/applethandle_p.h"
|
|
#include "private/extender_p.h"
|
|
#include "private/extenderapplet_p.h"
|
|
#include "private/extendergroup_p.h"
|
|
#include "private/extenderitem_p.h"
|
|
#include "private/extenderitemmimedata_p.h"
|
|
#include "widgets/label.h"
|
|
|
|
namespace Plasma
|
|
{
|
|
|
|
class ExtenderItemToolbox : public QGraphicsWidget
|
|
{
|
|
public:
|
|
ExtenderItemToolbox(QGraphicsWidget *parent)
|
|
: QGraphicsWidget(parent),
|
|
m_background(new FrameSvg(this))
|
|
{
|
|
m_background->setImagePath("widgets/extender-dragger");
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
updateTheme();
|
|
}
|
|
|
|
qreal iconSize()
|
|
{
|
|
return m_iconSize;
|
|
}
|
|
|
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
|
|
{
|
|
m_background->paintFrame(painter, option->exposedRect, option->exposedRect);
|
|
}
|
|
|
|
void updateTheme()
|
|
{
|
|
//Read the preferred icon size hint, look at the font size, and calculate the desired title bar
|
|
//icon height.
|
|
m_background->resize();
|
|
QSizeF size = m_background->elementSize("hint-preferred-icon-size");
|
|
size = size.expandedTo(QSizeF(KIconLoader::SizeSmall,KIconLoader::SizeSmall));
|
|
|
|
Plasma::Theme *theme = Plasma::Theme::defaultTheme();
|
|
QFont font = theme->font(Plasma::Theme::DefaultFont);
|
|
QFontMetrics fm(font);
|
|
m_iconSize = qMax(size.height(), (qreal) fm.height());
|
|
}
|
|
|
|
void setBackgroundPrefix(const QString &string)
|
|
{
|
|
if (string.isEmpty() || m_background->hasElementPrefix(string)) {
|
|
m_background->setElementPrefix(string);
|
|
update();
|
|
}
|
|
}
|
|
|
|
const QString backgroundPrefix() const
|
|
{
|
|
return m_background->prefix();
|
|
}
|
|
|
|
protected:
|
|
void resizeEvent(QGraphicsSceneResizeEvent *)
|
|
{
|
|
m_background->resizeFrame(size());
|
|
qreal left, top, right, bottom;
|
|
m_background->getMargins(left, top, right, bottom);
|
|
setContentsMargins(0, top, 0, bottom);
|
|
}
|
|
|
|
private:
|
|
FrameSvg *m_background;
|
|
QString m_prefix;
|
|
qreal m_iconSize;
|
|
};
|
|
|
|
ExtenderItem::ExtenderItem(Extender *hostExtender, uint extenderItemId)
|
|
: QGraphicsWidget(hostExtender),
|
|
d(new ExtenderItemPrivate(this, hostExtender))
|
|
{
|
|
Q_ASSERT(hostExtender);
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
|
|
//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 ExtenderItemToolbox(this);
|
|
d->toolboxLayout = new QGraphicsLinearLayout(d->toolbox);
|
|
|
|
//create items's configgroup
|
|
KConfigGroup cg = hostExtender->d->applet.data()->config("ExtenderItems");
|
|
KConfigGroup dg = KConfigGroup(&cg, QString::number(d->extenderItemId));
|
|
|
|
//create own layout
|
|
d->layout = new QGraphicsLinearLayout(Qt::Vertical, this);
|
|
d->layout->addItem(d->toolbox);
|
|
|
|
uint sourceAppletId = dg.readEntry("sourceAppletId", 0);
|
|
|
|
//check if we're creating a new item or reinstantiating an existing one.
|
|
d->collapseIcon = new IconWidget(d->toolbox);
|
|
d->collapseIcon->setCursor(Qt::ArrowCursor);
|
|
d->titleLabel = new Label(d->toolbox);
|
|
d->titleLabel->setWordWrap(false);
|
|
d->titleLabel->setAlignment(Qt::AlignCenter);
|
|
|
|
d->toolboxLayout->addItem(d->collapseIcon);
|
|
d->toolboxLayout->addItem(d->titleLabel);
|
|
d->toolboxLayout->setStretchFactor(d->titleLabel, 10);
|
|
|
|
if (!sourceAppletId) {
|
|
//The item is new
|
|
dg.writeEntry("sourceAppletPluginName", hostExtender->d->applet.data()->pluginName());
|
|
dg.writeEntry("sourceAppletId", hostExtender->d->applet.data()->id());
|
|
dg.writeEntry("extenderIconName", hostExtender->d->applet.data()->icon());
|
|
d->sourceApplet = hostExtender->d->applet.data();
|
|
d->collapseIcon->setIcon(KIcon(hostExtender->d->applet.data()->icon()));
|
|
} else {
|
|
//The item already exists.
|
|
d->name = dg.readEntry("extenderItemName", "");
|
|
d->titleLabel->setText(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->setIcon(iconName);
|
|
|
|
//Find the group if it's already there.
|
|
QString groupName = dg.readEntry("group", "");
|
|
d->group = hostExtender->d->findGroup(groupName);
|
|
|
|
//Find the sourceapplet.
|
|
Corona *corona = 0;
|
|
if (hostExtender && hostExtender->d->applet && hostExtender->d->applet.data()->containment()) {
|
|
corona = hostExtender->d->applet.data()->containment()->corona();
|
|
}
|
|
if (sourceAppletId == hostExtender->applet()->id()) {
|
|
d->sourceApplet = hostExtender->applet();
|
|
} else if (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.
|
|
if (d->sourceApplet) {
|
|
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);
|
|
|
|
//set the image paths, image sizes
|
|
d->themeChanged();
|
|
|
|
//show or hide the toolbox interface itmems
|
|
d->updateToolBox();
|
|
|
|
setAcceptsHoverEvents(true);
|
|
|
|
connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged()));
|
|
d->setMovable(d->extender->d->applet.data()->immutability() == Plasma::Mutable);
|
|
}
|
|
|
|
ExtenderItem::~ExtenderItem()
|
|
{
|
|
emit destroyed(this);
|
|
delete d;
|
|
}
|
|
|
|
KConfigGroup ExtenderItem::config() const
|
|
{
|
|
if (!d->extender->d->applet) {
|
|
return KConfigGroup();
|
|
}
|
|
|
|
KConfigGroup cg = d->extender->d->applet.data()->config("ExtenderItems");
|
|
KConfigGroup itemCg = KConfigGroup(&cg, QString::number(d->extenderItemId));
|
|
|
|
//we try to figure out if we are a transient ExtenderItem
|
|
//if we are, return an in memory config group (nothing will be saved on disk)
|
|
//if we aren't, return the ExtenderItems subgroup of our applet, as usual
|
|
if (d->transient) {
|
|
//create the dummy config group pointer if doesn't exists
|
|
if (!d->transientConfig) {
|
|
d->transientConfig = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
|
|
KConfigGroup dummyGroup = KConfigGroup(d->transientConfig, "ExtenderItems");
|
|
itemCg.reparent(&dummyGroup);
|
|
return itemCg;
|
|
}
|
|
KConfigGroup dummyGroup = KConfigGroup(d->transientConfig, "ExtenderItems");
|
|
dummyGroup = KConfigGroup(&dummyGroup, QString::number(d->extenderItemId));
|
|
return dummyGroup;
|
|
} else {
|
|
//if the dummy config pointer still exists, get rid of it
|
|
if (d->transientConfig) {
|
|
KConfigGroup dummyGroup = KConfigGroup(d->transientConfig, "ExtenderItems");
|
|
dummyGroup = KConfigGroup(&dummyGroup, QString::number(d->extenderItemId));
|
|
dummyGroup.reparent(&cg);
|
|
delete d->transientConfig.data();
|
|
d->transientConfig.clear();
|
|
return dummyGroup;
|
|
}
|
|
return itemCg;
|
|
}
|
|
}
|
|
|
|
void ExtenderItem::setTitle(const QString &title)
|
|
{
|
|
if (d->titleLabel->text() != title) {
|
|
d->titleLabel->setText(title);
|
|
config().writeEntry("extenderTitle", title);
|
|
update();
|
|
}
|
|
}
|
|
|
|
QString ExtenderItem::title() const
|
|
{
|
|
return d->titleLabel->text();
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (d->widget.data()) {
|
|
d->widget.data()->removeSceneEventFilter(this);
|
|
d->layout->removeItem(d->widget.data());
|
|
d->widget.data()->deleteLater();
|
|
}
|
|
|
|
if (!widget || !widget->isWidget()) {
|
|
return;
|
|
}
|
|
|
|
widget->setParentItem(this);
|
|
d->widget = static_cast<QGraphicsWidget *>(widget);
|
|
d->layout->insertItem(1, d->widget.data());
|
|
d->widget.data()->setVisible(!d->collapsed);
|
|
}
|
|
|
|
QGraphicsItem *ExtenderItem::widget() const
|
|
{
|
|
return d->widget.data();
|
|
}
|
|
|
|
void ExtenderItem::setIcon(const QIcon &icon)
|
|
{
|
|
if (d->collapseIcon->icon().isNull() || icon.cacheKey() != d->collapseIcon->icon().cacheKey()) {
|
|
d->iconName.clear();
|
|
d->collapseIcon->setIcon(icon);
|
|
d->collapseIcon->setVisible(!icon.isNull());
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
//themeChanged() has to now that by now, we're no longer dragging, even though the QDrag has not
|
|
//been entirely finished.
|
|
d->dragStarted = false;
|
|
|
|
ExtenderGroup *group = qobject_cast<ExtenderGroup*>(this);
|
|
QList<ExtenderItem*> childItems;
|
|
if (group) {
|
|
childItems = group->items();
|
|
}
|
|
|
|
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);
|
|
|
|
//move the configuration.
|
|
if (!d->transient && d->hostApplet() && (extender != d->extender)) {
|
|
KConfigGroup c = extender->d->applet.data()->config("ExtenderItems");
|
|
config().reparent(&c);
|
|
}
|
|
|
|
//and notify the applet of the item being detached, after the config has been moved.
|
|
emit d->extender->itemDetached(this);
|
|
|
|
setParentItem(extender);
|
|
setParent(extender);
|
|
if (d->extender) {
|
|
disconnect(d->extender->applet(), SIGNAL(immutabilityChanged(Plasma::ImmutabilityType)), this, SLOT(updateToolBox()));
|
|
}
|
|
d->extender = extender;
|
|
connect(d->extender->applet(), SIGNAL(immutabilityChanged(Plasma::ImmutabilityType)), this, SLOT(updateToolBox()));
|
|
|
|
//change parent.
|
|
extender->d->addExtenderItem(this, pos);
|
|
|
|
//cancel the timer.
|
|
if (d->expirationTimer && isDetached()) {
|
|
d->expirationTimer->stop();
|
|
delete d->expirationTimer;
|
|
d->expirationTimer = 0;
|
|
}
|
|
|
|
Corona *corona = qobject_cast<Corona*>(scene());
|
|
KConfigGroup extenderItemGroup(corona->config(), "DetachedExtenderItems");
|
|
|
|
if (isDetached()) {
|
|
kDebug() << "detached, adding entry to the global group";
|
|
KConfigGroup itemConfig = extenderItemGroup.group(QString::number(d->extenderItemId));
|
|
itemConfig.writeEntry("sourceAppletPluginName",
|
|
config().readEntry("sourceAppletPluginName", ""));
|
|
itemConfig.writeEntry("sourceAppletId",
|
|
config().readEntry("sourceAppletId", 0));
|
|
itemConfig.writeEntry("extenderItemName",
|
|
config().readEntry("extenderItemName", ""));
|
|
} else if (extenderItemGroup.hasGroup(QString::number(d->extenderItemId))) {
|
|
kDebug() << "no longer detached, removing entry from the global group";
|
|
extenderItemGroup.deleteGroup(QString::number(d->extenderItemId));
|
|
}
|
|
|
|
d->themeChanged();
|
|
|
|
//we might have to enable or disable the returnToSource button.
|
|
d->updateToolBox();
|
|
|
|
//invoke setGroup on all items belonging to this group, to make sure all children move to the
|
|
//new extender together with the group.
|
|
if (group) {
|
|
foreach (ExtenderItem *item, childItems) {
|
|
item->setGroup(group);
|
|
}
|
|
}
|
|
}
|
|
|
|
Extender *ExtenderItem::extender() const
|
|
{
|
|
return d->extender;
|
|
}
|
|
|
|
//TODO KDE5: only one setGroup()
|
|
void ExtenderItem::setGroup(ExtenderGroup *group)
|
|
{
|
|
setGroup(group, QPointF(-1, -1));
|
|
}
|
|
|
|
void ExtenderItem::setGroup(ExtenderGroup *group, const QPointF &pos)
|
|
{
|
|
if (isGroup()) {
|
|
//nesting extender groups is just insane. I don't think we'd even want to support that.
|
|
kWarning() << "Nesting ExtenderGroups is not supported";
|
|
return;
|
|
}
|
|
|
|
ExtenderGroup *oldGroup = d->group;
|
|
d->group = group;
|
|
|
|
if (group) {
|
|
d->toolbox->setBackgroundPrefix("grouped");
|
|
config().writeEntry("group", group->name());
|
|
//TODO: move to another extender if the group we set is actually detached.
|
|
if (group->extender() != extender()) {
|
|
kDebug() << "moving to another extender because we're joining a detached group.";
|
|
setExtender(group->extender());
|
|
}
|
|
group->d->addItemToGroup(this, pos);
|
|
} else {
|
|
if (d->extender->appearance() != Extender::NoBorders) {
|
|
d->toolbox->setBackgroundPrefix("root");
|
|
} else {
|
|
d->toolbox->setBackgroundPrefix(QString());
|
|
}
|
|
d->toolbox->setBackgroundPrefix(QString());
|
|
if (oldGroup) {
|
|
oldGroup->d->removeItemFromGroup(this);
|
|
}
|
|
config().deleteEntry("group");
|
|
}
|
|
d->dragStarted = false;
|
|
d->themeChanged();
|
|
}
|
|
|
|
ExtenderGroup *ExtenderItem::group() const
|
|
{
|
|
return d->group;
|
|
}
|
|
|
|
bool ExtenderItem::isGroup() const
|
|
{
|
|
return (config().readEntry("isGroup", false) && qobject_cast<const Plasma::ExtenderGroup *>(this));
|
|
}
|
|
|
|
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->dragStarted) {
|
|
//avoid being destroyed while we're being dragged.
|
|
return;
|
|
}
|
|
|
|
//remove global entry if needed.
|
|
Corona *corona = qobject_cast<Corona*>(scene());
|
|
if (corona) {
|
|
KConfigGroup extenderItemGroup(corona->config(), "DetachedExtenderItems");
|
|
if (extenderItemGroup.hasGroup(QString::number(d->extenderItemId))) {
|
|
extenderItemGroup.deleteGroup(QString::number(d->extenderItemId));
|
|
}
|
|
}
|
|
|
|
d->hostApplet()->config("ExtenderItems").deleteGroup(QString::number(d->extenderItemId));
|
|
d->extender->d->removeExtenderItem(this);
|
|
emit d->extender->itemDetached(this);
|
|
|
|
deleteLater();
|
|
}
|
|
|
|
void ExtenderItem::setCollapsed(bool collapsed)
|
|
{
|
|
config().writeEntry("isCollapsed", collapsed);
|
|
d->collapsed = collapsed;
|
|
d->collapseIcon->setToolTip(collapsed ? i18n("Expand this widget") : i18n("Collapse this widget"));
|
|
if (d->widget.data()) {
|
|
d->widget.data()->setVisible(!collapsed);
|
|
if (collapsed) {
|
|
d->layout->removeItem(d->widget.data());
|
|
} else {
|
|
d->layout->insertItem(1, d->widget.data());
|
|
}
|
|
updateGeometry();
|
|
|
|
if (extender()) {
|
|
extender()->d->adjustMinimumSize();
|
|
static_cast<QGraphicsLayoutItem *>(extender()->d->mainWidget)->updateGeometry();
|
|
if (group()) {
|
|
group()->layout()->invalidate();
|
|
static_cast<QGraphicsLayoutItem *>(group())->updateGeometry();
|
|
}
|
|
|
|
extender()->d->adjustSize();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExtenderItem::returnToSource()
|
|
{
|
|
if (!d || !d->sourceApplet) {
|
|
return;
|
|
}
|
|
|
|
if (d->sourceApplet->d) {
|
|
setExtender(d->sourceApplet->extender());
|
|
}
|
|
}
|
|
|
|
void ExtenderItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
|
|
{
|
|
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, option->exposedRect, option->exposedRect);
|
|
}
|
|
}
|
|
|
|
void ExtenderItem::moveEvent(QGraphicsSceneMoveEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
//not needed anymore, but here for binary compatibility
|
|
}
|
|
|
|
void ExtenderItem::resizeEvent(QGraphicsSceneResizeEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
//resize the applet background
|
|
d->background->resizeFrame(size());
|
|
//d->resizeContent(size());
|
|
}
|
|
|
|
void ExtenderItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (!(d->dragHandleRect().contains(event->pos())) ||
|
|
d->extender->d->applet.data()->immutability() != Plasma::Mutable) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ExtenderItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
QPoint mousePressPos = event->buttonDownPos(Qt::LeftButton).toPoint();
|
|
if (!(event->buttons() & Qt::LeftButton) ||
|
|
(event->pos().toPoint() - mousePressPos).manhattanLength()
|
|
< QApplication::startDragDistance()) {
|
|
return;
|
|
}
|
|
|
|
if (!d->extender->d->applet) {
|
|
return;
|
|
}
|
|
|
|
//Start the drag:
|
|
d->dragStarted = true;
|
|
QPointF curPos = pos();
|
|
|
|
//remove item from the layout, and add it somewhere off screen so we can render it to a pixmap,
|
|
//without other widgets interefing.
|
|
d->extender->itemRemovedEvent(this);
|
|
Corona *corona = qobject_cast<Corona*>(scene());
|
|
corona->addOffscreenWidget(this);
|
|
|
|
//update the borders, since while dragging, we want all of theme.
|
|
d->themeChanged();
|
|
|
|
//create a view to render the ExtenderItem and it's contents to a pixmap and set up a painter on
|
|
//a pixmap.
|
|
QGraphicsView view(scene());
|
|
QSize screenSize(view.mapFromScene(sceneBoundingRect()).boundingRect().size());
|
|
QPixmap pixmap(screenSize);
|
|
pixmap.fill(Qt::transparent);
|
|
QPainter p(&pixmap);
|
|
|
|
//the following is necesarry to avoid having an offset when rendering the widget into the
|
|
//pixmap.
|
|
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
view.setFrameShape(QFrame::NoFrame);
|
|
|
|
//aim the view and render.
|
|
view.resize(screenSize);
|
|
view.setSceneRect(sceneBoundingRect());
|
|
view.render(&p, QRectF(QPointF(0, 0), pixmap.size()), QRect(QPoint(0, 0), screenSize));
|
|
|
|
//create the necesarry mimedata.
|
|
ExtenderItemMimeData *mimeData = new ExtenderItemMimeData();
|
|
mimeData->setExtenderItem(this);
|
|
mimeData->setPointerOffset(mousePressPos);
|
|
|
|
//Hide empty internal extender containers when we drag the last item away. Avoids having
|
|
//an ugly empty applet on the desktop temporarily.
|
|
ExtenderApplet *extenderApplet = qobject_cast<ExtenderApplet*>(d->extender->d->applet.data());
|
|
if (extenderApplet && d->extender->attachedItems().count() < 2 &&
|
|
extenderApplet->formFactor() != Plasma::Horizontal &&
|
|
extenderApplet->formFactor() != Plasma::Vertical) {
|
|
kDebug() << "leaving the internal extender container, so hide the applet and it's handle.";
|
|
extenderApplet->hide();
|
|
}
|
|
|
|
ExtenderGroup *group = qobject_cast<ExtenderGroup*>(this);
|
|
bool collapsedGroup = false;
|
|
if (isGroup()) {
|
|
collapsedGroup = group->d->collapsed;
|
|
group->collapseGroup();
|
|
}
|
|
|
|
if (!isGroup() && this->group()) {
|
|
setGroup(0);
|
|
}
|
|
|
|
//and execute the drag.
|
|
QWidget *dragParent = extender()->d->applet.data()->view();
|
|
QDrag *drag = new QDrag(dragParent);
|
|
drag->setPixmap(pixmap);
|
|
drag->setMimeData(mimeData);
|
|
drag->setHotSpot(mousePressPos);
|
|
|
|
Qt::DropAction action = drag->exec();
|
|
|
|
corona->removeOffscreenWidget(this);
|
|
d->dragStarted = false;
|
|
|
|
if (!action || !drag->target()) {
|
|
//we weren't moved, so reinsert the item in our current layout.
|
|
//TODO: make it into a stand-alone window?
|
|
d->themeChanged();
|
|
d->extender->itemAddedEvent(this, curPos);
|
|
if (extenderApplet) {
|
|
extenderApplet->show();
|
|
}
|
|
}
|
|
|
|
if (isGroup() && !collapsedGroup) {
|
|
group->expandGroup();
|
|
}
|
|
}
|
|
|
|
void ExtenderItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (d->dragHandleRect().contains(event->pos())) {
|
|
d->toggleCollapse();
|
|
}
|
|
}
|
|
|
|
bool ExtenderItem::sceneEventFilter(QGraphicsItem *, QEvent *)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void ExtenderItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
//not needed anymore, but here for binary compatibility
|
|
}
|
|
|
|
void ExtenderItem::hoverMoveEvent(QGraphicsSceneHoverEvent *)
|
|
{
|
|
}
|
|
|
|
void ExtenderItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
|
|
{
|
|
}
|
|
|
|
QSizeF ExtenderItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
|
|
{
|
|
return QGraphicsWidget::sizeHint(which, constraint);
|
|
}
|
|
|
|
void ExtenderItem::setTransient(const bool transient)
|
|
{
|
|
d->transient = transient;
|
|
}
|
|
|
|
bool ExtenderItem::isTransient() const
|
|
{
|
|
return d->transient;
|
|
}
|
|
|
|
ExtenderItemPrivate::ExtenderItemPrivate(ExtenderItem *extenderItem, Extender *hostExtender)
|
|
: q(extenderItem),
|
|
toolbox(0),
|
|
extender(hostExtender),
|
|
sourceApplet(0),
|
|
group(0),
|
|
background(new FrameSvg(extenderItem)),
|
|
collapseIcon(0),
|
|
expirationTimer(0),
|
|
dragStarted(false),
|
|
destroyActionVisibility(false),
|
|
collapsed(false),
|
|
transient(false)
|
|
{
|
|
}
|
|
|
|
ExtenderItemPrivate::~ExtenderItemPrivate()
|
|
{
|
|
delete widget.data();
|
|
}
|
|
|
|
//returns a Rect containing the area of the detachable where the draghandle will be drawn.
|
|
QRectF ExtenderItemPrivate::dragHandleRect()
|
|
{
|
|
return toolbox->boundingRect();
|
|
}
|
|
|
|
void ExtenderItemPrivate::toggleCollapse()
|
|
{
|
|
q->setCollapsed(!q->isCollapsed());
|
|
}
|
|
|
|
void ExtenderItemPrivate::updateToolBox()
|
|
{
|
|
Q_ASSERT(toolbox);
|
|
Q_ASSERT(toolboxLayout);
|
|
|
|
|
|
QAction *closeAction = actions.value("close");
|
|
QAction *returnToSourceAction = actions.value("extenderItemReturnToSource");
|
|
bool returnToSourceVisibility = q->isDetached() && sourceApplet && (hostApplet()->immutability() == Plasma::Mutable);
|
|
int closeIndex = -1;
|
|
int returnToSourceIndex = -1;
|
|
const int startingIndex = 2; // collapse item is index 0, title label is 1
|
|
int lastIndex = 2;
|
|
const QSizeF widgetSize = collapseIcon->sizeFromIconSize(toolbox->iconSize());
|
|
|
|
QSet<QAction*> shownActions = actionsInOrder.toSet();
|
|
|
|
QHash<QAction *, QGraphicsWidget *> actionWidgets;
|
|
for (int index = startingIndex; index < toolboxLayout->count(); ++index) {
|
|
QGraphicsWidget *widget = dynamic_cast<QGraphicsWidget*>(toolboxLayout->itemAt(index));
|
|
QAction *widgetAction = 0;
|
|
|
|
if (!widget) {
|
|
continue;
|
|
} else if (qobject_cast<IconWidget*>(widget)) {
|
|
widgetAction = static_cast<IconWidget*>(widget)->action();
|
|
} else if (qobject_cast<PushButton*>(widget)) {
|
|
widgetAction = static_cast<PushButton*>(widget)->action();
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
|
|
if (closeIndex == -1 && destroyActionVisibility &&
|
|
closeAction && widgetAction == closeAction) {
|
|
closeIndex = index;
|
|
continue;
|
|
}
|
|
|
|
if (returnToSourceIndex == -1 && returnToSourceVisibility &&
|
|
returnToSourceAction && widgetAction == returnToSourceAction) {
|
|
returnToSourceIndex = index;
|
|
continue;
|
|
}
|
|
|
|
if (shownActions.contains(widgetAction)) {
|
|
actionWidgets.insert(widgetAction, widget);
|
|
continue;
|
|
}
|
|
|
|
toolboxLayout->removeAt(index);
|
|
widget->deleteLater();
|
|
}
|
|
|
|
|
|
// ensure the collapseIcon is the correct size.
|
|
collapseIcon->setMinimumSize(widgetSize);
|
|
collapseIcon->setMaximumSize(widgetSize);
|
|
|
|
//add the actions that are actually set to visible.
|
|
foreach (QAction *action, actionsInOrder) {
|
|
if (action->isVisible() && action != closeAction) {
|
|
IconWidget *icon = qobject_cast<IconWidget*>(actionWidgets.value(action));
|
|
PushButton *button = qobject_cast<PushButton*>(actionWidgets.value(action));
|
|
|
|
if (action->icon().isNull() && !action->text().isNull()) {
|
|
if (!button) {
|
|
button = new PushButton(q);
|
|
button->setAction(action);
|
|
}
|
|
|
|
button->setMinimumHeight(widgetSize.height());
|
|
button->setMaximumHeight(widgetSize.height());
|
|
button->setCursor(Qt::ArrowCursor);
|
|
toolboxLayout->insertItem(startingIndex, button);
|
|
++lastIndex;
|
|
} else {
|
|
if (!icon) {
|
|
icon = new IconWidget(q);
|
|
icon->setAction(action);
|
|
}
|
|
|
|
if (action->icon().isNull()) {
|
|
icon->setText(action->text());
|
|
}
|
|
icon->setMinimumSize(widgetSize);
|
|
icon->setMaximumSize(widgetSize);
|
|
icon->setCursor(Qt::ArrowCursor);
|
|
toolboxLayout->insertItem(startingIndex, icon);
|
|
++lastIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
//add the returntosource icon if we are detached, and have a source applet.
|
|
if (returnToSourceVisibility && returnToSourceIndex == -1) {
|
|
IconWidget *returnToSourceIcon = new IconWidget(q);
|
|
if (!returnToSourceAction) {
|
|
returnToSourceAction = new QAction(q);
|
|
returnToSourceAction->setToolTip(i18n("Reattach"));
|
|
actions.insert("extenderItemReturnToSource", returnToSourceAction);
|
|
QObject::connect(returnToSourceAction, SIGNAL(triggered()), q, SLOT(returnToSource()));
|
|
}
|
|
|
|
returnToSourceIcon->setAction(returnToSourceAction);
|
|
returnToSourceIcon->setSvg("widgets/configuration-icons", "return-to-source");
|
|
returnToSourceIcon->setMinimumSize(widgetSize);
|
|
returnToSourceIcon->setMaximumSize(widgetSize);
|
|
returnToSourceIcon->setCursor(Qt::ArrowCursor);
|
|
|
|
if (closeIndex == -1) {
|
|
toolboxLayout->addItem(returnToSourceIcon);
|
|
} else {
|
|
toolboxLayout->insertItem(closeIndex - 1, returnToSourceIcon);
|
|
}
|
|
++lastIndex;
|
|
}
|
|
|
|
//add the close icon if desired.
|
|
if (destroyActionVisibility && closeIndex == -1) {
|
|
IconWidget *destroyButton = new IconWidget(q);
|
|
if (!closeAction) {
|
|
closeAction = new QAction(q);
|
|
actions.insert("close", closeAction);
|
|
if (returnToSourceAction) {
|
|
returnToSourceAction->setToolTip(i18n("Close"));
|
|
}
|
|
QObject::connect(closeAction, SIGNAL(triggered()), q, SLOT(destroy()));
|
|
}
|
|
|
|
destroyButton->setAction(closeAction);
|
|
destroyButton->setSvg("widgets/configuration-icons", "close");
|
|
destroyButton->setMinimumSize(widgetSize);
|
|
destroyButton->setMaximumSize(widgetSize);
|
|
destroyButton->setCursor(Qt::ArrowCursor);
|
|
toolboxLayout->addItem(destroyButton);
|
|
++lastIndex;
|
|
}
|
|
|
|
//to keep the text really centered
|
|
toolboxLayout->setItemSpacing(0, KIconLoader::SizeSmall * (lastIndex - 2));
|
|
if (lastIndex == 2) {
|
|
if (QApplication::layoutDirection() == Qt::RightToLeft) {
|
|
toolboxLayout->setContentsMargins(KIconLoader::SizeSmall, 0, 0, 0);
|
|
} else {
|
|
toolboxLayout->setContentsMargins(0, 0, KIconLoader::SizeSmall, 0);
|
|
}
|
|
} else {
|
|
toolboxLayout->setContentsMargins(0, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
Applet *ExtenderItemPrivate::hostApplet() const
|
|
{
|
|
if (extender) {
|
|
return extender->d->applet.data();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void ExtenderItemPrivate::themeChanged()
|
|
{
|
|
kDebug();
|
|
if (dragStarted) {
|
|
background->setImagePath("opaque/widgets/extender-background");
|
|
background->setEnabledBorders(FrameSvg::AllBorders);
|
|
} else {
|
|
background->setImagePath("widgets/extender-background");
|
|
background->setEnabledBorders(extender->enabledBordersForItem(q));
|
|
}
|
|
|
|
qreal left, top, right, bottom;
|
|
background->getMargins(left, top, right, bottom);
|
|
layout->setContentsMargins(left, top, right, bottom);
|
|
|
|
if (group) {
|
|
toolbox->setBackgroundPrefix("grouped");
|
|
} else {
|
|
if (extender->items().count() <= 1 || extender->items().first() == q || extender->appearance() != Extender::NoBorders) {
|
|
toolbox->setBackgroundPrefix("root");
|
|
} else {
|
|
toolbox->setBackgroundPrefix(QString());
|
|
}
|
|
}
|
|
|
|
toolbox->updateTheme();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExtenderItemPrivate::setMovable(bool movable)
|
|
{
|
|
if (movable) {
|
|
titleLabel->setCursor(Qt::OpenHandCursor);
|
|
toolbox->setCursor(Qt::OpenHandCursor);
|
|
} else {
|
|
titleLabel->unsetCursor();
|
|
toolbox->unsetCursor();
|
|
}
|
|
}
|
|
|
|
uint ExtenderItemPrivate::s_maxExtenderItemId = 0;
|
|
|
|
} // namespace Plasma
|
|
|
|
#include "extenderitem.moc"
|