/* * Copyright 2008, 2009 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 "extender.h" #include #include #include #include #include #include #include #include "applet.h" #include "containment.h" #include "corona.h" #include "dialog.h" #include "extendergroup.h" #include "extenderitem.h" #include "framesvg.h" #include "paintutils.h" #include "popupapplet.h" #include "svg.h" #include "theme.h" #include "widgets/label.h" #include "widgets/scrollwidget.h" #include "private/applet_p.h" #include "private/applethandle_p.h" #include "private/extender_p.h" #include "private/extenderapplet_p.h" #include "private/extenderitem_p.h" #include "private/extenderitemmimedata_p.h" #include "private/popupapplet_p.h" namespace Plasma { Spacer::Spacer(QGraphicsItem *parent) : QGraphicsWidget(parent) { } Spacer::~Spacer() { } void Spacer::setMargins(qreal left, qreal top, qreal right, qreal bottom) { m_left = left; m_top = top; m_right = right; m_bottom = bottom; } void Spacer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option) Q_UNUSED(widget) painter->setRenderHint(QPainter::Antialiasing); QPainterPath p = Plasma::PaintUtils::roundedRectangle( contentsRect().adjusted(m_left, m_top, -m_right, -m_bottom), 4); QColor c = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor); c.setAlphaF(0.3); painter->fillPath(p, c); } Extender::Extender(Applet *applet) : QGraphicsWidget(applet), d(new ExtenderPrivate(applet, this)) { //At multiple places in the extender code, we make the assumption that an applet doesn't have //more then one extender. If a second extender is created, destroy the first one to avoid leaks. if (applet->d->extender) { kWarning() << "Applet already has an extender, and can have only one extender." << "The previous extender will be destroyed."; delete applet->d->extender.data(); } else if (PopupApplet *popup = qobject_cast(applet)) { if (!popup->d->graphicsWidget) { // ensure the popup gets a dialog with us as the graphics widget popup->d->popupConstraintsEvent(SizeConstraint); } } applet->d->extender = this; setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->scrollWidget = new ScrollWidget(this); d->scrollWidget->setOverflowBordersVisible(false); d->scrollWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->mainWidget = new QGraphicsWidget(d->scrollWidget); d->scrollWidget->setWidget(d->mainWidget); d->mainWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(d->scrollWidget, SIGNAL(viewportGeometryChanged(const QRectF &)), this, SLOT(viewportGeometryChanged(const QRectF &))); d->layout = new QGraphicsLinearLayout(d->mainWidget); d->layout->setOrientation(Qt::Vertical); d->layout->setContentsMargins(0, 0, 0, 0); d->layout->setSpacing(0); QGraphicsLinearLayout *lay = new QGraphicsLinearLayout(Qt::Vertical, this); lay->addItem(d->scrollWidget); setContentsMargins(0, 0, 0, 0); lay->setContentsMargins(0, 0, 0, 0); d->loadExtenderItems(); setAcceptDrops(true); } Extender::~Extender() { d->destroying = true; // when deleting items that are connected to us, it can happen that // other items which are in groups may get deleted as well. so we first // build a new list of guarded pointers, and then use that list. that // way when items are deleted behind our back, we are still safe. // FIXME: having groups and items in the same collection is probably a mistake, // so would be a good candidate for a refactoring exercise QList > guardedItems; foreach (ExtenderItem *item, d->attachedExtenderItems) { guardedItems << QWeakPointer(item); } d->attachedExtenderItems.clear(); foreach (const QWeakPointer &guardedItem, guardedItems) { ExtenderItem *item = guardedItem.data(); if (item) { item->disconnect(this); delete item; } } delete d; } void Extender::setEmptyExtenderMessage(const QString &message) { d->emptyExtenderMessage = message; if (d->emptyExtenderLabel) { d->emptyExtenderLabel->setText(message); } } QString Extender::emptyExtenderMessage() const { return d->emptyExtenderMessage; } QList Extender::items() const { QList result; //FIXME: a triple nested loop ... ew. there should be a more efficient way to do this //iterate through all extenders we can find and check each extenders source applet. if (!d->applet) { return QList(); } Containment *containment = d->applet.data()->containment(); if (!containment) { return result; } foreach (Containment *c, containment->corona()->containments()) { foreach (Applet *applet, c->applets()) { if (applet->d->extender) { foreach (ExtenderItem *item, applet->d->extender.data()->d->attachedExtenderItems) { if (item->d->sourceApplet == d->applet.data()) { result.append(item); } } } } } return result; } QList Extender::attachedItems() const { return d->attachedExtenderItems; } QList Extender::detachedItems() const { QList result; //FIXME: a triple nested loop ... ew. there should be a more efficient way to do this //iterate through all extenders we can find and check each extenders source applet. if (!d->applet) { return QList(); } Containment *containment = d->applet.data()->containment(); if (!containment) { return result; } foreach (Containment *c, containment->corona()->containments()) { foreach (Applet *applet, c->applets()) { if (applet->d->extender) { foreach (ExtenderItem *item, applet->d->extender.data()->attachedItems()) { if (item->d->sourceApplet == d->applet.data() && item->isDetached()) { result.append(item); } } } } } return result; } ExtenderItem *Extender::item(const QString &name) const { // chances are the item is in our own extender, so check there first foreach (ExtenderItem *item, d->attachedExtenderItems) { if (item->d->sourceApplet == d->applet.data() && item->name() == name) { return item; } } // maybe it's been moved elsewhere, so lets search through the entire tree of elements //FIXME: a triple nested loop ... ew. there should be a more efficient way to do this //iterate through all extenders we can find and check each extenders source applet. if (!d->applet) { return 0; } Containment *containment = d->applet.data()->containment(); if (!containment) { return 0; } QList containments; if (containment->corona()) { containments = containment->corona()->containments(); } else { containments << containment; } foreach (Containment *c, containments) { foreach (Applet *applet, c->applets()) { if (applet->d->extender) { if (applet->d->extender.data() == this) { // skip it, we checked it already continue; } if (!applet->d->extender) { continue; } foreach (ExtenderItem *item, applet->d->extender.data()->attachedItems()) { if (item->d->sourceApplet == d->applet.data() && item->name() == name) { return item; } } } } } return 0; } ExtenderGroup *Extender::group(const QString &name) const { return qobject_cast(item(name)); } bool Extender::hasItem(const QString &name) const { if (item(name)) { return true; } if (!d->applet) { return false; } //if item(name) returns false, that doesn't mean that the item doesn't exist, just that it has //not been instantiated yet. check to see if there's mention of this item existing in the //plasma config's section DetachedExtenderItems Corona *corona = qobject_cast(scene()); if (!corona) { return false; } KConfigGroup extenderItemGroup(corona->config(), "DetachedExtenderItems"); foreach (const QString &extenderItemId, extenderItemGroup.groupList()) { KConfigGroup cg = extenderItemGroup.group(extenderItemId); if (uint(cg.readEntry("sourceAppletId", 0)) == d->applet.data()->id() && cg.readEntry("extenderItemName", "") == name && cg.readEntry("sourceAppletPluginName", "") == d->applet.data()->pluginName()) { return true; } } return false; } void Extender::setAppearance(Appearance appearance) { if (d->appearance == appearance) { return; } d->appearance = appearance; d->updateBorders(); } Extender::Appearance Extender::appearance() const { return d->appearance; } QList Extender::groups() const { QList result; foreach (ExtenderItem *item, d->attachedExtenderItems) { if (item->isGroup() && !result.contains(item->group())) { ExtenderGroup *group = qobject_cast(item); if (group) { result.append(group); } } } return result; } Applet *Extender::applet() const { return d->applet.data(); } void Extender::saveState() { foreach (ExtenderItem *item, attachedItems()) { item->config().writeEntry("extenderItemPosition", item->geometry().y()); } } QVariant Extender::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionHasChanged) { emit geometryChanged(); } return QGraphicsWidget::itemChange(change, value); } void Extender::resizeEvent(QGraphicsSceneResizeEvent *event) { QGraphicsWidget::resizeEvent(event); emit geometryChanged(); } void Extender::mousePressEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event) if (!d->applet) { return; } PopupApplet *popupApplet = qobject_cast(d->applet.data()); if (isEmpty() && popupApplet) { popupApplet->hidePopup(); } } void Extender::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) { event->accept(); const ExtenderItemMimeData *mimeData = qobject_cast(event->mimeData()); if (mimeData) { itemHoverEnterEvent(mimeData->extenderItem()); PopupApplet *popupApplet = qobject_cast(d->applet.data()); if (popupApplet) { popupApplet->showPopup(); } } } } void Extender::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) { const ExtenderItemMimeData *mimeData = qobject_cast(event->mimeData()); if (mimeData) { itemHoverMoveEvent(mimeData->extenderItem(), event->pos()); d->setPositionFromDragPosition(event->scenePos()); } } } void Extender::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) { const ExtenderItemMimeData *mimeData = qobject_cast(event->mimeData()); if (mimeData) { itemHoverLeaveEvent(mimeData->extenderItem()); //Some logic to conveniently show/hide popups or applets when logical. Extender *sourceExtender = mimeData->extenderItem()->d->extender; //Hide popups when they're not the extender where we started, and we're leaving the //extender. Use a small timeout here, to avoid accidental hides of extenders we're //targetting. PopupApplet *popupApplet = qobject_cast(d->applet.data()); if (popupApplet && sourceExtender != this) { kDebug() << "leaving another extender then the extender we started, so hide the popup."; popupApplet->showPopup(250); } //Hide popups when we drag the last item away. if (popupApplet && sourceExtender == this && (attachedItems().count() < 2)) { kDebug() << "leaving the extender, and there are no more attached items so hide the popup."; popupApplet->hidePopup(); } //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(d->applet.data()); if (extenderApplet && sourceExtender == this && 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(); AppletHandle *handle = dynamic_cast(extenderApplet->parentItem()); if (handle) { handle->hide(); } } } } } void Extender::dropEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())) { const ExtenderItemMimeData *mimeData = qobject_cast(event->mimeData()); if (mimeData) { mimeData->extenderItem()->setExtender(this, event->pos()); QApplication::restoreOverrideCursor(); itemHoverLeaveEvent(0); } } } void Extender::itemAddedEvent(ExtenderItem *item, const QPointF &pos) { if (!item->group()) { if (pos == QPointF(-1, -1)) { //if it was already there, reposition d->layout->removeItem(item); //if just plain adding an item, add it at a sane position: if (appearance() == BottomUpStacked) { //at the top d->layout->insertItem(0, item); } else { //at the bottom d->layout->addItem(item); } } else { kDebug() << "inserting at" << pos << d->insertIndexFromPos(pos) << item->size(); d->layout->insertItem(d->insertIndexFromPos(pos), item); kDebug() << item->size(); } } d->adjustMinimumSize(); //remove the empty extender message if needed. d->updateEmptyExtenderLabel(); d->updateBorders(); d->adjustSize(); } void Extender::itemRemovedEvent(ExtenderItem *item) { if (d->destroying) { return; } d->layout->removeItem(item); if (d->spacerWidget) { d->layout->removeItem(d->spacerWidget); delete d->spacerWidget; d->spacerWidget = 0; } d->adjustMinimumSize(); //add the empty extender message if needed. d->updateEmptyExtenderLabel(); d->updateBorders(); d->layout->updateGeometry(); static_cast(d->scrollWidget)->updateGeometry(); updateGeometry(); d->adjustSize(); } void Extender::itemHoverEnterEvent(ExtenderItem *item) { itemHoverMoveEvent(item, QPointF(0, 0)); } void Extender::itemHoverMoveEvent(ExtenderItem *item, const QPointF &pos) { if (d->spacerWidget && d->spacerWidget->geometry().contains(pos)) { return; } //Make sure we remove any spacer that might already be in the layout. if (d->spacerWidget) { d->layout->removeItem(d->spacerWidget); } int insertIndex = d->insertIndexFromPos(pos); //Create a widget that functions as spacer, and add that to the layout. if (!d->spacerWidget) { Spacer *widget = new Spacer(this); qreal left, top, right, bottom; d->background->getMargins(left, top, right, bottom); widget->setMargins(left, 4, right, 4); widget->setMinimumSize(item->minimumSize()); widget->setPreferredSize(item->preferredSize()); widget->setMaximumSize(item->maximumSize()); widget->setSizePolicy(item->sizePolicy()); d->spacerWidget = widget; } d->layout->insertItem(insertIndex, d->spacerWidget); //Make sure we remove any 'no detachables' label that might be there, and update the layout. d->updateEmptyExtenderLabel(); } void Extender::itemHoverLeaveEvent(ExtenderItem *item) { Q_UNUSED(item); if (d->spacerWidget) { //Remove any trace of the spacer widget. d->layout->removeItem(d->spacerWidget); delete d->spacerWidget; d->spacerWidget = 0; d->currentSpacerIndex = -1; d->updateEmptyExtenderLabel(); } } FrameSvg::EnabledBorders Extender::enabledBordersForItem(ExtenderItem *item) const { if (d->layout->count() < 1) { return 0; } ExtenderItem *topItem = dynamic_cast(d->layout->itemAt(0)); ExtenderItem *bottomItem = dynamic_cast(d->layout->itemAt(d->layout->count() - 1)); FrameSvg::EnabledBorders borders = FrameSvg::NoBorder; if (item->group()) { return FrameSvg::NoBorder; } else if (d->appearance == TopDownStacked && bottomItem != item) { borders = FrameSvg::LeftBorder | FrameSvg::BottomBorder | FrameSvg::RightBorder; } else if (d->appearance == BottomUpStacked && topItem != item) { borders = FrameSvg::LeftBorder | FrameSvg::TopBorder | FrameSvg::RightBorder; } else if (d->appearance != NoBorders) { borders = FrameSvg::LeftBorder | FrameSvg::RightBorder; } else { return FrameSvg::NoBorder; } if (d->scrollWidget->viewportGeometry().height() < d->mainWidget->boundingRect().height()) { if (QApplication::layoutDirection() == Qt::RightToLeft) { borders &= ~FrameSvg::LeftBorder; } else { borders &= ~FrameSvg::RightBorder; } } //someone (i.e. a Dialog) told the extender to disable some border? borders &= ~d->disabledBordersHint; return borders; } ExtenderPrivate::ExtenderPrivate(Applet *applet, Extender *extender) : q(extender), applet(applet), background(new FrameSvg(extender)), disabledBordersHint(FrameSvg::NoBorder), currentSpacerIndex(-1), spacerWidget(0), emptyExtenderMessage(QString()), emptyExtenderLabel(0), appearance(Extender::NoBorders), destroying(false) { background->setImagePath("widgets/extender-background"); } ExtenderPrivate::~ExtenderPrivate() { } void ExtenderPrivate::addExtenderItem(ExtenderItem *item, const QPointF &pos) { if (attachedExtenderItems.contains(item)) { pendingItems.remove(item); q->itemAddedEvent(item, pos); return; } QObject::connect(item, SIGNAL(destroyed(Plasma::ExtenderItem*)), q, SLOT(extenderItemDestroyed(Plasma::ExtenderItem*))); attachedExtenderItems.append(item); q->itemHoverLeaveEvent(item); pendingItems[item] = pos; QTimer::singleShot(0, q, SLOT(delayItemAddedEvent())); } void ExtenderPrivate::removeExtenderItem(ExtenderItem *item) { attachedExtenderItems.removeAll(item); //collapse the popupapplet if the last item is removed. if (attachedExtenderItems.isEmpty()) { PopupApplet *popupApplet = qobject_cast(applet.data()); if (popupApplet) { popupApplet->hidePopup(); } } q->itemRemovedEvent(item); } int ExtenderPrivate::insertIndexFromPos(const QPointF &pos) const { int insertIndex = -1; //XXX: duplicated from panel if (pos != QPointF(-1, -1)) { for (int i = 0; i < layout->count(); ++i) { QRectF siblingGeometry = layout->itemAt(i)->geometry(); qreal middle = (siblingGeometry.top() + siblingGeometry.bottom()) / 2.0; if (pos.y() < middle) { insertIndex = i; break; } else if (pos.y() <= siblingGeometry.bottom()) { insertIndex = i + 1; break; } } } return insertIndex; } void ExtenderPrivate::loadExtenderItems() { if (!applet) { return; } KConfigGroup cg = applet.data()->config("ExtenderItems"); //first create a list of extenderItems, and then sort them on their position, so the items get //recreated in the correct order. QList > groupList; foreach (const QString &extenderItemId, cg.groupList()) { KConfigGroup dg = cg.group(extenderItemId); groupList.append(qMakePair(dg.readEntry("extenderItemPosition", 0), extenderItemId)); } qSort(groupList); //iterate over the extender items for (int i = 0; i < groupList.count(); i++) { QPair pair = groupList[i]; KConfigGroup dg = cg.group(pair.second); //load the relevant settings. QString extenderItemId = dg.name(); QString extenderItemName = dg.readEntry("extenderItemName", ""); QString appletName = dg.readEntry("sourceAppletPluginName", ""); uint sourceAppletId = dg.readEntry("sourceAppletId", 0); bool temporarySourceApplet = false; kDebug() << "applet id = " << applet.data()->id(); kDebug() << "sourceappletid = " << sourceAppletId; //find the source applet. Applet *sourceApplet = 0; if (applet.data()->id() == sourceAppletId) { // it's ours! sourceApplet = applet.data(); } else { // maybe it's foreign? Containment *containment = applet.data()->containment(); if (containment) { Corona *corona = containment->corona(); if (sourceAppletId == q->applet()->id()) { sourceApplet = q->applet(); } else { foreach (Containment *containment, corona->containments()) { foreach (Applet *applet, containment->applets()) { if (applet->id() == sourceAppletId) { sourceApplet = applet; } } } } } } //There is no source applet. We just instantiate one just for the sake of creating //detachables. if (!sourceApplet) { kDebug() << "creating a temporary applet as factory"; sourceApplet = Applet::load(appletName); temporarySourceApplet = true; //TODO: maybe add an option to applet to indicate that it shouldn't be deleted after //having used it as factory. } if (!sourceApplet) { kDebug() << "sourceApplet is null? appletName = " << appletName; kDebug() << " extenderItemId = " << extenderItemId; } else { ExtenderItem *item; if (dg.readEntry("isGroup", false)) { item = new ExtenderGroup(q, extenderItemId.toInt()); } else { item = new ExtenderItem(q, extenderItemId.toInt()); } sourceApplet->initExtenderItem(item); item->d->sourceApplet = sourceApplet; if (temporarySourceApplet) { delete sourceApplet; } } } } void ExtenderPrivate::updateBorders() { foreach (ExtenderItem *item, attachedExtenderItems) { if (item && (item->d->background->enabledBorders() != q->enabledBordersForItem(item))) { //call themeChanged to change the backgrounds enabled borders, and move all contained //widgets according to it's changed margins. item->d->themeChanged(); } } } void ExtenderPrivate::delayItemAddedEvent() { QHash::const_iterator i = pendingItems.constBegin(); while (i != pendingItems.constEnd()) { q->itemAddedEvent(i.key(), i.value()); ++i; } pendingItems.clear(); } void ExtenderPrivate::updateEmptyExtenderLabel() { if (q->isEmpty() && !emptyExtenderLabel && !emptyExtenderMessage.isEmpty() && !spacerWidget ) { //add the empty extender label. emptyExtenderLabel = new Label(q); emptyExtenderLabel->setAlignment(Qt::AlignCenter); emptyExtenderLabel->setText(emptyExtenderMessage); qreal left, top, right, bottom; background->getMargins(left, top, right, bottom); emptyExtenderLabel->nativeWidget()->setMargin(left + right); emptyExtenderLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); layout->addItem(emptyExtenderLabel); } else if (!q->isEmpty() && emptyExtenderLabel) { //remove the empty extender label. layout->removeItem(emptyExtenderLabel); delete emptyExtenderLabel; emptyExtenderLabel = 0; } } void ExtenderPrivate::adjustMinimumSize() { //FIXME: hardcoded number for the scrollbar scrollWidget->setMinimumWidth(mainWidget->effectiveSizeHint(Qt::MinimumSize).width() + 32); //FIXME: hardcoded number scrollWidget->setMinimumHeight(qMin((qreal)300, mainWidget->effectiveSizeHint(Qt::MinimumSize).height())); } void ExtenderPrivate::setPositionFromDragPosition(const QPointF &pos) { const qreal ratio = (q->mapFromScene(pos).y()/scrollWidget->size().height()); mainWidget->setPos(mainWidget->pos().x(), 30 + (ratio *(scrollWidget->size().height() - mainWidget->size().height() - 30))); } ExtenderGroup *ExtenderPrivate::findGroup(const QString &name) const { foreach (ExtenderItem *item, attachedExtenderItems) { if (item->isGroup() && item->name() == name) { return qobject_cast(item); } } return 0; } void ExtenderPrivate::extenderItemDestroyed(Plasma::ExtenderItem *item) { if (item && attachedExtenderItems.contains(item)) { removeExtenderItem(item); } else if (pendingItems.contains(item)) { pendingItems.remove(item); } else { attachedExtenderItems.removeAll(item); } } void ExtenderPrivate::viewportGeometryChanged(const QRectF &rect) { if (appearance != Extender::TopDownStacked && appearance != Extender::BottomUpStacked) { scrollbarVisible = (rect.height() > mainWidget->boundingRect().height()); return; } bool scroll; if (rect.height() >= mainWidget->boundingRect().height()) { scroll = false; scrollWidget->setContentsMargins(0, 0, 0, 0); } else { scroll = true; if (QApplication::layoutDirection() == Qt::RightToLeft) { scrollWidget->setContentsMargins(background->marginSize(RightMargin), 0, 0, 0); } else { scrollWidget->setContentsMargins(0, 0, background->marginSize(RightMargin), 0); } } if (scroll != scrollbarVisible) { scrollbarVisible = scroll; updateBorders(); } } void ExtenderPrivate::setDisabledBordersHint(const FrameSvg::EnabledBorders borders) { if (disabledBordersHint == borders) { return; } disabledBordersHint = borders; foreach (Plasma::ExtenderItem *item, attachedExtenderItems) { item->d->themeChanged(); } } void ExtenderPrivate::adjustSize() { QRect screenRect; QSizeF size = mainWidget->effectiveSizeHint(Qt::PreferredSize); if (applet && applet.data()->containment() && applet.data()->containment()->corona()) { screenRect = applet.data()->containment()->corona()->screenGeometry(applet.data()->containment()->screen()); } q->resize(qMin(screenRect.width()/3, (int)size.width()), qMin(screenRect.height()/3, (int)size.height())); } bool Extender::isEmpty() const { //It's empty if it doesn't have items or has only group that are empty and autohide foreach (ExtenderItem *item, d->attachedExtenderItems) { if (!item->isGroup()) { return false; } else { //a static_cast here should be safe, it's not the case apparently ExtenderGroup *group = qobject_cast(item); if (group && (!group->autoHide() || group->items().size() > 0)) { return false; } } } return true; } } // Plasma namespace #include "extender.moc"