//////////////////////////////////////////////////////////////////////// // appletscontainer.cpp // // // // Copyright 2007 by Alex Merry // // Copyright 2008 by Alexis Ménard // // Copyright 2008 by Aaron Seigo // // Copyright 2009 by Marco Martin // // Copyright 2010 by Igor Oliveira openbossa.org> // // // // 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 Street, Fifth Floor, Boston, MA // // 02110-1301 USA // ///////////////////////////////////////////////////////////////////////// #include "appletscontainer.h" #include "applettitlebar.h" #include "appletsview.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Plasma; AppletsContainer::AppletsContainer(AppletsView *parent) : QGraphicsWidget(parent), m_scrollWidget(parent), m_orientation(Qt::Vertical), m_viewportSize(size()), m_containment(0), m_automaticAppletLayout(true), m_expandAll(false), m_appletsPerColumn(1), m_appletsPerRow(1), m_viewScrollState(QAbstractAnimation::Stopped), m_toolBox(0) { setFlag(QGraphicsItem::ItemHasNoContents); m_mainLayout = new QGraphicsLinearLayout(this); m_mainLayout->setContentsMargins(0,0,0,0); m_viewSyncTimer = new QTimer(this); m_viewSyncTimer->setSingleShot(true); connect(m_viewSyncTimer, SIGNAL(timeout()), this, SLOT(syncView())); m_viewportGeometryUpdateTimer = new QTimer(this); m_viewportGeometryUpdateTimer->setSingleShot(true); connect(m_viewportGeometryUpdateTimer, SIGNAL(timeout()), this, SLOT(updateViewportGeometry())); connect(m_scrollWidget, SIGNAL(viewportGeometryChanged(const QRectF &)), this, SLOT(viewportGeometryChanged(const QRectF &))); connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged())); connect(m_scrollWidget, SIGNAL(scrollStateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), this, SLOT(scrollStateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); themeChanged(); m_background = new Plasma::FrameSvg(this); m_background->setImagePath("widgets/newspaper-background"); syncBorders(); connect(m_background, SIGNAL(repaintNeeded()), SLOT(syncBorders())); } AppletsContainer::~AppletsContainer() { } void AppletsContainer::setAutomaticAppletLayout(const bool automatic) { m_automaticAppletLayout = automatic; } bool AppletsContainer::automaticAppletLayout() const { return m_automaticAppletLayout; } void AppletsContainer::themeChanged() { QFont font = Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont); QFontMetrics fm(font); m_mSize = fm.boundingRect("M").size(); updateViewportGeometry(); } void AppletsContainer::syncColumnSizes() { const int margin = 4 + (m_mainLayout->count() - 1) * m_mainLayout->spacing(); QSizeF viewportSize = m_scrollWidget->viewportGeometry().size(); QSizeF maxSize; //try to figure out the column size from the applets size hints if (m_orientation == Qt::Vertical && m_containment) { foreach (Plasma::Applet *applet, m_containment->applets()) { QSizeF appletSize = applet->effectiveSizeHint(Qt::PreferredSize); if (appletSize.width() > maxSize.width()) { maxSize.setWidth(appletSize.width()); } if (appletSize.height() > maxSize.height()) { maxSize.setHeight(appletSize.height()); } } const QSizeF sizeFromHints = maxSize * m_mainLayout->count(); //a bit of snap to avoid contents just too large if (qAbs(sizeFromHints.width() - viewportSize.width()) > 128) { viewportSize = sizeFromHints; } } for (int i = 0; i < m_mainLayout->count(); ++i) { QGraphicsLinearLayout *lay = dynamic_cast(m_mainLayout->itemAt(i)); if (m_orientation == Qt::Vertical) { lay->setMaximumWidth(viewportSize.width()/m_mainLayout->count()-margin); lay->setMinimumWidth(viewportSize.width()/m_mainLayout->count()-margin); lay->setMaximumHeight(-1); lay->setMinimumHeight(-1); } else { lay->setMaximumHeight(viewportSize.height()/m_mainLayout->count()-margin); lay->setMinimumHeight(viewportSize.height()/m_mainLayout->count()-margin); lay->setMaximumWidth(-1); lay->setMinimumWidth(-1); } } } void AppletsContainer::updateSize() { //Layouts are to happy to cache their hints, invalidate everything for (int i = 0; i < m_mainLayout->count(); ++i) { QGraphicsLinearLayout *lay = dynamic_cast(m_mainLayout->itemAt(i)); lay->invalidate(); } m_mainLayout->invalidate(); QSizeF hint = sizeHint(Qt::PreferredSize, QSize()); int toolBoxSpace = 0; if (m_orientation == Qt::Horizontal) { if (m_toolBox) { toolBoxSpace = m_viewportSize.width()/m_appletsPerColumn; } resize(hint.width() + toolBoxSpace, qMin(size().height(), m_scrollWidget->viewportGeometry().height())); if (m_toolBox) { m_toolBox->setPos(QPoint(size().width() - toolBoxSpace/2, size().height()/2) - QPoint(m_toolBox->size().width()/2, m_toolBox->size().height()/2)); } } else { if (m_toolBox) { toolBoxSpace = m_viewportSize.height()/m_appletsPerRow; } resize(qMin(size().width(), m_scrollWidget->viewportGeometry().width()), hint.height() + toolBoxSpace); if (m_toolBox) { m_toolBox->setPos(QPoint(size().width()/2, size().height() - toolBoxSpace/2) - QPoint(m_toolBox->size().width()/2, m_toolBox->size().height()/2)); } } } void AppletsContainer::setExpandAll(const bool expand) { if (m_expandAll == expand) { return; } m_expandAll = expand; if (!m_containment) { return; } if (expand) { foreach (Plasma::Applet *applet, m_containment->applets()) { if (applet->effectiveSizeHint(Qt::MinimumSize).height() > KIconLoader::SizeSmall) { applet->setPreferredHeight(-1); } QList titles = applet->findChildren("TitleBar"); if (!titles.isEmpty()) { titles.first()->setActive(true); } } } else { foreach (Plasma::Applet *applet, m_containment->applets()) { if (m_orientation == Qt::Vertical) { if (applet == m_currentApplet.data()) { applet->setPreferredHeight(optimalAppletSize(applet, true).height()); } else { applet->setPreferredHeight(optimalAppletSize(applet, false).height()); } } else { applet->setPreferredSize(-1, -1); applet->setPreferredWidth((m_scrollWidget->viewportGeometry().size().width()/2)-8); } QList titles = applet->findChildren("TitleBar"); if (!titles.isEmpty()) { titles.first()->setActive(applet == m_currentApplet.data()); } } } updateSnapSize(); updateSize(); } bool AppletsContainer::expandAll() const { return m_expandAll; } Qt::Orientation AppletsContainer::orientation() const { return m_orientation; } Plasma::Containment *AppletsContainer::containment() const { return m_containment; } QGraphicsLinearLayout *AppletsContainer::addColumn() { QGraphicsLinearLayout *lay = new QGraphicsLinearLayout(m_orientation); lay->setContentsMargins(0,0,0,0); lay->setSpacing(0); m_mainLayout->addItem(lay); QGraphicsWidget *spacer = new QGraphicsWidget(this); spacer->setPreferredSize(0, 0); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); lay->addItem(spacer); syncColumnSizes(); return lay; } void AppletsContainer::cleanupColumns() { //clean up all empty columns for (int i = 0; i < count(); ++i) { QGraphicsLinearLayout *lay = dynamic_cast(m_mainLayout->itemAt(i)); if (!lay) { continue; } if (lay->count() == 1) { removeColumn(i); } } } void AppletsContainer::removeColumn(int column) { QGraphicsLinearLayout *lay = dynamic_cast(m_mainLayout->itemAt(column)); if (!lay) { return; } m_mainLayout->removeAt(column); for (int i = 0; i < lay->count(); ++i) { QGraphicsLayoutItem *item = lay->itemAt(i); QGraphicsWidget *widget = dynamic_cast(item); Plasma::Applet *applet = qobject_cast(widget); //find a new home for the applet if (applet) { layoutApplet(applet, applet->pos()); //delete spacers } else if (widget) { widget->deleteLater(); } } syncColumnSizes(); delete lay; } void AppletsContainer::layoutApplet(Plasma::Applet* applet, const QPointF &pos) { if (!m_automaticAppletLayout) { return; } QGraphicsLinearLayout *lay = 0; for (int i = 0; i < m_mainLayout->count(); ++i) { QGraphicsLinearLayout *candidateLay = dynamic_cast(m_mainLayout->itemAt(i)); //normally should never happen if (!candidateLay) { continue; } if (m_orientation == Qt::Horizontal) { if (pos.y() < candidateLay->geometry().bottom()) { lay = candidateLay; break; } //vertical } else { if (pos.x() < candidateLay->geometry().right()) { lay = candidateLay; break; } } } //couldn't decide: is the last column empty? if (!lay) { QGraphicsLinearLayout *candidateLay = dynamic_cast(m_mainLayout->itemAt(m_mainLayout->count()-1)); if (candidateLay && candidateLay->count() == 1) { lay = candidateLay; } } //give up, make a new column if (!lay) { lay = addColumn(); } int insertIndex = -1; QPointF localPos = mapToItem(this, pos); //if localPos is (-1,-1) insert at the end of the Newspaper if (localPos != QPoint(-1, -1)) { for (int i = 0; i < lay->count(); ++i) { QGraphicsLayoutItem *li = lay->itemAt(i); QRectF siblingGeometry = li->geometry(); if (m_orientation == Qt::Horizontal) { qreal middle = (siblingGeometry.left() + siblingGeometry.right()) / 2.0; if (localPos.x() < middle) { insertIndex = i; break; } else if (localPos.x() <= siblingGeometry.right()) { insertIndex = i + 1; break; } } else { //Vertical qreal middle = (siblingGeometry.top() + siblingGeometry.bottom()) / 2.0; if (localPos.y() < middle) { insertIndex = i; break; } else if (localPos.y() <= siblingGeometry.bottom()) { insertIndex = i + 1; break; } } } } if (insertIndex == -1) { lay->insertItem(lay->count()-1, applet); } else { lay->insertItem(qMin(insertIndex, lay->count()-1), applet); } connect(applet, SIGNAL(sizeHintChanged(Qt::SizeHint)), this, SIGNAL(appletSizeHintChanged())); updateSize(); createAppletTitle(applet); syncColumnSizes(); } void AppletsContainer::addApplet(Plasma::Applet* applet, const int row, const int column) { QGraphicsLinearLayout *lay; //give up, make a new column if (column < 0 || column >= m_mainLayout->count()) { lay = addColumn(); } else { lay = static_cast(m_mainLayout->itemAt(column)); } if (row <= 0) { lay->insertItem(lay->count()-1, applet); } else { lay->insertItem(qMin(row, lay->count()-1), applet); } connect(applet, SIGNAL(sizeHintChanged(Qt::SizeHint)), this, SIGNAL(appletSizeHintChanged())); updateSize(); createAppletTitle(applet); syncColumnSizes(); } void AppletsContainer::createAppletTitle(Plasma::Applet *applet) { QList titles = applet->findChildren("TitleBar"); if (!titles.isEmpty()) { return; } AppletTitleBar *appletTitleBar = new AppletTitleBar(applet); appletTitleBar->setParent(applet); appletTitleBar->show(); appletTitleBar->setActive(m_expandAll); if (!m_containment) { m_containment = applet->containment(); if (m_containment) { m_toolBox = Plasma::AbstractToolBox::load("org.kde.mobiletoolbox", QVariantList(), m_containment); if (m_toolBox) { m_toolBox->setParentItem(this); m_toolBox->show(); m_toolBox->addTool(m_containment->action("add widgets")); } } } if (m_orientation == Qt::Horizontal) { applet->setPreferredSize(-1, -1); applet->setPreferredWidth((m_scrollWidget->viewportGeometry().size().width()/2)-8); } else if (m_expandAll) { if (applet->effectiveSizeHint(Qt::MinimumSize).height() > KIconLoader::SizeSmall) { applet->setPreferredHeight(-1); } } else { applet->setPreferredHeight(optimalAppletSize(applet, false).height()); } } void AppletsContainer::setOrientation(Qt::Orientation orientation) { m_orientation = orientation; m_mainLayout->setOrientation(orientation==Qt::Vertical?Qt::Horizontal:Qt::Vertical); } int AppletsContainer::count() const { return m_mainLayout->count(); } QGraphicsLayoutItem *AppletsContainer::itemAt(int i) { return m_mainLayout->itemAt(i); } QSizeF AppletsContainer::optimalAppletSize(Plasma::Applet *applet, const bool maximized) const { if (!applet) { return QSizeF(); } const QSizeF minNormalAppletSize(m_viewportSize.width() / m_appletsPerColumn, m_viewportSize.height() / m_appletsPerRow); //FIXME: this change of fixed preferred height could cause a relayout, unfortunately there is no other way int preferred = applet->preferredHeight(); applet->setPreferredHeight(-1); QSizeF normalAppletSize = QSizeF(applet->effectiveSizeHint(Qt::PreferredSize)); if (normalAppletSize.height() <= minNormalAppletSize.height()/2) { normalAppletSize.setHeight(minNormalAppletSize.height()/2); } else { normalAppletSize.setHeight(minNormalAppletSize.height()); } if (normalAppletSize.width() <= minNormalAppletSize.width()/2) { normalAppletSize.setWidth(minNormalAppletSize.width()/2); } else { normalAppletSize.setWidth(minNormalAppletSize.width()); } //4,4 is the size of the margins of the scroll view normalAppletSize -= QSize(4/m_appletsPerColumn, 4/m_appletsPerRow); if (maximized) { QSizeF size = QSizeF(applet->effectiveSizeHint(Qt::PreferredSize)).boundedTo(m_viewportSize).expandedTo(normalAppletSize); applet->setPreferredHeight(preferred-4); return size; } else { applet->setPreferredHeight(preferred); return normalAppletSize; } } QSizeF AppletsContainer::viewportSize() const { return m_viewportSize; } void AppletsContainer::viewportGeometryChanged(const QRectF &geometry) { Q_UNUSED(geometry) m_viewportGeometryUpdateTimer->start(250); } void AppletsContainer::scrollStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) { Q_UNUSED(oldState) m_viewScrollState = newState; } void AppletsContainer::updateViewportGeometry() { m_viewportSize = m_scrollWidget->viewportGeometry().size(); //each applet with an optimal of 40 columns, 15 lines of text m_appletsPerColumn = qMax((qreal)1, m_viewportSize.width() / (m_mSize.width()*40)); m_appletsPerRow = qMax((qreal)1, m_viewportSize.height() / (m_mSize.height()*15)); if (!m_containment || (m_expandAll && m_orientation != Qt::Horizontal)) { syncColumnSizes(); return; } foreach (Plasma::Applet *applet, m_containment->applets()) { if (m_orientation == Qt::Vertical) { if (applet == m_currentApplet.data()) { applet->setPreferredHeight(optimalAppletSize(applet, true).height()); } else { applet->setPreferredHeight(optimalAppletSize(applet, false).height()); } } else { applet->setPreferredSize(-1, -1); applet->setPreferredWidth(optimalAppletSize(applet, false).width()); } } updateSnapSize(); syncColumnSizes(); } void AppletsContainer::updateSnapSize() { if (m_orientation == Qt::Horizontal || (!m_expandAll && !m_currentApplet)) { m_scrollWidget->setSnapSize(QSize(m_viewportSize.width()/m_appletsPerColumn, m_viewportSize.height()/m_appletsPerRow)); } else { m_scrollWidget->setSnapSize(QSizeF()); } } void AppletsContainer::mousePressEvent(QGraphicsSceneMouseEvent *event) { event->accept(); } void AppletsContainer::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event) if (m_orientation == Qt::Horizontal) { return; } setCurrentApplet(0); QGraphicsWidget::mouseReleaseEvent(event); } void AppletsContainer::setCurrentApplet(Plasma::Applet *applet) { if (m_currentApplet.data() == applet) { return; } if (m_currentApplet) { m_currentApplet.data()->setPreferredHeight(optimalAppletSize(m_currentApplet.data(), false).height()); QList titles = m_currentApplet.data()->findChildren("TitleBar"); if (!titles.isEmpty()) { titles.first()->setActive(false); } } m_currentApplet = applet; if (applet) { applet->setPreferredHeight(optimalAppletSize(applet, true).height()); updateSize(); m_viewSyncTimer->start(200); QList titles = applet->findChildren("TitleBar"); if (!titles.isEmpty()) { titles.first()->setActive(true); } } updateSnapSize(); emit appletActivated(m_currentApplet.data()); } void AppletsContainer::syncView() { Plasma::Applet *applet = m_currentApplet.data(); if (applet && m_viewScrollState == QAbstractAnimation::Stopped) { m_scrollWidget->ensureRectVisible(QRectF(applet->pos(), QSizeF(applet->size().width(), applet->preferredHeight()))); } } Plasma::Applet *AppletsContainer::currentApplet() const { return m_currentApplet.data(); } void AppletsContainer::resizeEvent(QGraphicsSceneResizeEvent *event) { m_background->resizeFrame(event->newSize()); QGraphicsWidget::resizeEvent(event); } void AppletsContainer::syncBorders() { qreal left, top, right, bottom = 0.0; if (m_background->isValid()) { setFlag(QGraphicsItem::ItemHasNoContents, false); m_background->getMargins(left, top, right, bottom); } else { setFlag(QGraphicsItem::ItemHasNoContents); } setContentsMargins(left, top, right, bottom); } void AppletsContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option) Q_UNUSED(widget) m_background->paintFrame(painter); } #include "moc_appletscontainer.cpp"