/* * Copyright 2008 by Rob Scheepmaker * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 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", ""); 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())); //create the toolbox. d->toolbox = new QGraphicsWidget(this); d->toolboxLayout = new QGraphicsLinearLayout(d->toolbox); d->toolbox->setLayout(d->toolboxLayout); //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 { if (!d->widget) { return true; } else { return !d->widget->isVisible(); } } 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) { 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(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->sourceApplet) { return; } 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(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(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), 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(widget); QGraphicsView *currentV = 0; if (hostApplet()) { currentV = qobject_cast(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(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 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(w); if (dialog) { if (dialog->isVisible() && dialog->geometry().contains(pos)) { v = qobject_cast(dialog->layout()->itemAt(0)->widget()); if (v) { return v->mapToScene(v->mapFromGlobal(pos)); } } } else { v = qobject_cast(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(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(o); QMutableHashIterator hit(actions); while (hit.hasNext()) { if (hit.next().value() == action) { hit.remove(); break; } } QMutableListIterator lit(actionsInOrder); while (lit.hasNext()) { if (lit.next() == action) { lit.remove(); break; } } } uint ExtenderItemPrivate::s_maxExtenderItemId = 0; } // namespace Plasma #include "extenderitem.moc"