/* * Copyright 2008 Marco Martin <notmart@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program 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 General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tabbar.h" #include <QGraphicsLinearLayout> #include <QGraphicsLayoutItem> #include <QGraphicsProxyWidget> #include <QGraphicsScene> #include <QGraphicsSceneWheelEvent> #include <QIcon> #include <QMenu> #include <QPainter> #include <QParallelAnimationGroup> #include <QString> #include <QStyleOption> #include <kdebug.h> #include "animator.h" #include "animations/animation.h" #include "graphicsview/private/nativetabbar_p.h" #include "private/themedwidgetinterface_p.h" #include "theme.h" namespace Plasma { class TabBarProxy : public QGraphicsProxyWidget { public: TabBarProxy(QGraphicsWidget *parent) : QGraphicsProxyWidget(parent) { native = new NativeTabBar(); native->setAttribute(Qt::WA_NoSystemBackground); setWidget(native); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); //Don't paint the child widgets static_cast<NativeTabBar *>(QGraphicsProxyWidget::widget())->render( painter, QPoint(0, 0), QRegion(), 0); } NativeTabBar *native; }; class TabBarPrivate : public ThemedWidgetInterface<TabBar> { public: TabBarPrivate(TabBar *parent) : ThemedWidgetInterface<TabBar>(parent), tabProxy(0), currentIndex(0), tabWidgetMode(true), oldPageAnimId(-1), newPageAnimId(-1), tabBarShown(true) { } ~TabBarPrivate() { } void updateTabWidgetMode(); void slidingCompleted(QGraphicsItem *item); void slidingNewPageCompleted(); void slidingOldPageCompleted(); void shapeChanged(const KTabBar::Shape shape); TabBarProxy *tabProxy; QList<QGraphicsWidget *> pages; QGraphicsWidget *emptyTabBarSpacer; QGraphicsLinearLayout *mainLayout; QGraphicsLinearLayout *tabWidgetLayout; QGraphicsLinearLayout *tabBarLayout; int currentIndex; bool tabWidgetMode; QWeakPointer<QGraphicsWidget> oldPage; QWeakPointer<QGraphicsWidget> newPage; int oldPageAnimId; int newPageAnimId; Animation *oldPageAnim; Animation *newPageAnim; QParallelAnimationGroup *animGroup; bool tabBarShown; QWeakPointer<QGraphicsWidget> firstPositionWidget; QWeakPointer<QGraphicsWidget> lastPositionWidget; }; void TabBarPrivate::updateTabWidgetMode() { if (!tabBarShown) { return; } bool tabWidget = false; foreach (QGraphicsWidget *page, pages) { if (page->preferredSize() != QSize(0, 0)) { tabWidget = true; break; } } if (tabWidget != tabWidgetMode) { if (tabWidget) { mainLayout->removeAt(0); tabBarLayout->insertItem(1, tabProxy); mainLayout->addItem(tabWidgetLayout); } else { mainLayout->removeAt(0); tabBarLayout->removeAt(1); mainLayout->addItem(tabProxy); } } //always show the tabbar //FIXME: Qt BUG: calling show on a child of an hidden item it shows it anyways //so we avoid to call it if the parent is hidden if (!tabWidget && q->isVisible()) { q->setTabBarShown(true); } tabWidgetMode = tabWidget; if (!tabWidgetMode) { q->setMinimumSize(QSize(0, 0)); q->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); } else { tabProxy->native->setMinimumSize(QSize(0,0)); tabProxy->setMinimumSize(QSize(0,0)); } } void TabBarPrivate::slidingNewPageCompleted() { if (newPage) { tabWidgetLayout->addItem(newPage.data()); } newPageAnimId = -1; mainLayout->invalidate(); emit q->currentChanged(currentIndex); q->setFlags(0); } void TabBarPrivate::slidingOldPageCompleted() { QGraphicsWidget *item = oldPageAnim->targetWidget(); oldPageAnimId = -1; if (item) { item->hide(); } q->setFlags(0); } void TabBarPrivate::shapeChanged(const QTabBar::Shape shape) { //FIXME: QGraphicsLinearLayout doesn't have setDirection, so for now // North is equal to south and East is equal to West switch (shape) { case QTabBar::RoundedWest: case QTabBar::TriangularWest: case QTabBar::RoundedEast: case QTabBar::TriangularEast: q->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); tabBarLayout->setOrientation(Qt::Vertical); tabWidgetLayout->setOrientation(Qt::Horizontal); tabWidgetLayout->itemAt(0)->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); if (tabWidgetLayout->count() > 1) { tabWidgetLayout->itemAt(1)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } tabProxy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); break; case QTabBar::RoundedSouth: case QTabBar::TriangularSouth: case QTabBar::RoundedNorth: case QTabBar::TriangularNorth: default: q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); tabBarLayout->setOrientation(Qt::Horizontal); tabWidgetLayout->setOrientation(Qt::Vertical); tabWidgetLayout->itemAt(0)->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); if (tabWidgetLayout->count() > 1) { tabWidgetLayout->itemAt(1)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } tabProxy->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } tabProxy->setPreferredSize(tabProxy->native->sizeHint()); } TabBar::TabBar(QGraphicsWidget *parent) : QGraphicsWidget(parent), d(new TabBarPrivate(this)) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); setContentsMargins(0,0,0,0); d->tabProxy = new TabBarProxy(this); d->tabWidgetLayout = new QGraphicsLinearLayout(Qt::Vertical); d->tabBarLayout = new QGraphicsLinearLayout(Qt::Horizontal); d->tabWidgetLayout->setContentsMargins(0,0,0,0); d->mainLayout = new QGraphicsLinearLayout(Qt::Horizontal); d->mainLayout->addItem(d->tabWidgetLayout); setLayout(d->mainLayout); d->mainLayout->setContentsMargins(0,0,0,0); //simulate a page until there isn't one //needed to make the widget resize well when there are no tab added d->emptyTabBarSpacer = new QGraphicsWidget(this); d->tabWidgetLayout->addItem(d->tabBarLayout); d->tabWidgetLayout->addItem(d->emptyTabBarSpacer); //tabBar is centered, so a stretch at begin one at the end d->tabBarLayout->addStretch(); d->tabBarLayout->addItem(d->tabProxy); d->tabBarLayout->addStretch(); d->tabBarLayout->setContentsMargins(0,0,0,0); //d->tabBarLayout->setStretchFactor(d->tabProxy, 2); d->newPageAnim = Animator::create(Animator::SlideAnimation); d->oldPageAnim = Animator::create(Animator::SlideAnimation); d->animGroup = new QParallelAnimationGroup(this); d->animGroup->addAnimation(d->newPageAnim); d->animGroup->addAnimation(d->oldPageAnim); connect(d->tabProxy->native, SIGNAL(currentChanged(int)), this, SLOT(setCurrentIndex(int))); connect(d->tabProxy->native, SIGNAL(shapeChanged(QTabBar::Shape)), this, SLOT(shapeChanged(QTabBar::Shape))); connect(d->newPageAnim, SIGNAL(finished()), this, SLOT(slidingNewPageCompleted())); connect(d->oldPageAnim, SIGNAL(finished()), this, SLOT(slidingOldPageCompleted())); d->initTheming(); } TabBar::~TabBar() { delete d; } int TabBar::insertTab(int index, const QIcon &icon, const QString &label, QGraphicsLayoutItem *content) { QGraphicsWidget *page = new QGraphicsWidget(this); page->setContentsMargins(0,0,0,0); page->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); if (content) { if (content->isLayout()) { page->setLayout(static_cast<QGraphicsLayout *>(content)); } else { QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical, page); layout->setContentsMargins(0,0,0,0); layout->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); layout->addItem(content); page->setLayout(layout); } } else { page->setPreferredSize(0, 0); } d->pages.insert(qBound(0, index, d->pages.count()), page); if (d->pages.count() == 1) { d->tabWidgetLayout->removeItem(d->emptyTabBarSpacer); d->tabWidgetLayout->addItem(page); page->setVisible(true); page->setEnabled(true); } else { page->setVisible(false); page->setEnabled(false); } d->tabProxy->setPreferredSize(d->tabProxy->native->sizeHint()); d->updateTabWidgetMode(); int actualIndex = d->tabProxy->native->insertTab(index, icon, label); d->currentIndex = d->tabProxy->native->currentIndex(); d->tabProxy->setPreferredSize(d->tabProxy->native->sizeHint()); d->updateTabWidgetMode(); return actualIndex; } int TabBar::insertTab(int index, const QString &label, QGraphicsLayoutItem *content) { return insertTab(index, QIcon(), label, content); } int TabBar::addTab(const QIcon &icon, const QString &label, QGraphicsLayoutItem *content) { return insertTab(d->pages.count(), icon, label, content); } int TabBar::addTab(const QString &label, QGraphicsLayoutItem *content) { return insertTab(d->pages.count(), QIcon(), label, content); } int TabBar::currentIndex() const { return d->tabProxy->native->currentIndex(); } void TabBar::resizeEvent(QGraphicsSceneResizeEvent * event) { if (!d->tabWidgetMode) { d->tabProxy->setMinimumSize(event->newSize().toSize()); setMinimumSize(QSize(0, 0)); setMinimumHeight(d->tabProxy->widget()->minimumSizeHint().height()); setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); } else { setMinimumSize(QSize(-1, -1)); d->tabProxy->native->setMinimumSize(QSize(0,0)); } } void TabBar::setCurrentIndex(int index) { if (index >= d->pages.count() || d->pages.count() < 2 || d->currentIndex == index) { return; } d->oldPage = d->pages.value(d->currentIndex); if (d->oldPage) { d->tabWidgetLayout->removeItem(d->oldPage.data()); } if (index >= 0) { d->newPage = d->pages.value(index); } setFlags(QGraphicsItem::ItemClipsChildrenToShape); //if an animation was in rogress hide everything to avoid an inconsistent state if (d->animGroup->state() != QAbstractAnimation::Stopped) { foreach (QGraphicsWidget *page, d->pages) { page->hide(); } d->animGroup->stop(); } if (d->newPage) { d->newPage.data()->show(); d->newPage.data()->setEnabled(true); } if (d->oldPage) { d->oldPage.data()->show(); d->oldPage.data()->setEnabled(false); } if (d->newPage && d->oldPage) { //FIXME: it seems necessary to resiz the thing 2 times to have effect d->newPage.data()->resize(1,1); d->newPage.data()->resize(d->oldPage.data()->size()); QRect beforeCurrentGeom(d->oldPage.data()->geometry().toRect()); beforeCurrentGeom.moveTopRight(beforeCurrentGeom.topLeft()); if (index > d->currentIndex) { d->newPage.data()->setPos(d->oldPage.data()->geometry().topRight()); d->newPageAnim->setProperty("movementDirection", Animation::MoveLeft); d->newPageAnim->setProperty("distancePointF", QPointF(d->oldPage.data()->size().width(), 0)); d->newPageAnim->setTargetWidget(d->newPage.data()); d->oldPageAnim->setProperty("movementDirection", Animation::MoveLeft); d->oldPageAnim->setProperty("distancePointF", QPointF(beforeCurrentGeom.width(), 0)); d->oldPageAnim->setTargetWidget(d->oldPage.data()); d->animGroup->start(); } else { d->newPage.data()->setPos(beforeCurrentGeom.topLeft()); d->newPageAnim->setProperty("movementDirection", Animation::MoveRight); d->newPageAnim->setProperty("distancePointF", QPointF(d->oldPage.data()->size().width(), 0)); d->newPageAnim->setTargetWidget(d->newPage.data()); d->oldPageAnim->setProperty("movementDirection", Animation::MoveRight); d->oldPageAnim->setProperty("distancePointF", QPointF(d->oldPage.data()->size().width(), 0)); d->oldPageAnim->setTargetWidget(d->oldPage.data()); d->animGroup->start(); } } else if (d->newPage) { d->tabWidgetLayout->addItem(d->newPage.data()); } d->currentIndex = index; d->tabProxy->native->setCurrentIndex(index); } int TabBar::count() const { return d->pages.count(); } void TabBar::removeTab(int index) { if (index >= d->pages.count() || index < 0) { return; } d->newPageAnim->stop(); d->oldPageAnim->stop(); int oldCurrentIndex = d->tabProxy->native->currentIndex(); d->tabProxy->native->removeTab(index); d->currentIndex = oldCurrentIndex; int currentIndex = d->tabProxy->native->currentIndex(); if (oldCurrentIndex == index) { d->tabWidgetLayout->removeAt(1); if (d->tabProxy->native->count() > 0) { setCurrentIndex(currentIndex >= oldCurrentIndex ? currentIndex + 1 : currentIndex); } } QGraphicsWidget *page = d->pages.takeAt(index); scene()->removeItem(page); page->deleteLater(); if (d->pages.count() > 0) { d->updateTabWidgetMode(); } else { d->tabWidgetLayout->addItem(d->emptyTabBarSpacer); } } QGraphicsLayoutItem *TabBar::takeTab(int index) { if (index >= d->pages.count()) { return 0; } int oldCurrentIndex = d->tabProxy->native->currentIndex(); d->tabProxy->native->removeTab(index); int currentIndex = d->tabProxy->native->currentIndex(); if (oldCurrentIndex == index) { d->tabWidgetLayout->removeAt(1); if (d->tabProxy->native->count() > 0) { setCurrentIndex(currentIndex >= oldCurrentIndex ? currentIndex + 1 : currentIndex); } } QGraphicsWidget *page = d->pages.takeAt(index); QGraphicsLayoutItem *returnItem = 0; QGraphicsLayout *lay = page->layout(); if (lay && lay->count() == 1) { returnItem = lay->itemAt(0); lay->removeAt(0); } else { returnItem = lay; } if (returnItem) { returnItem->setParentLayoutItem(0); if (QGraphicsItem *item = returnItem->graphicsItem()) { item->setParentItem(0); } } page->setLayout(0); scene()->removeItem(page); page->deleteLater(); if (oldCurrentIndex != currentIndex) { setCurrentIndex(currentIndex); } d->updateTabWidgetMode(); d->tabProxy->setPreferredSize(d->tabProxy->native->sizeHint()); return returnItem; } QGraphicsLayoutItem *TabBar::tabAt(int index) { if (index >= d->pages.count()) { return 0; } QGraphicsWidget *page = d->pages.value(index); QGraphicsLayoutItem *returnItem = 0; QGraphicsLayout *lay = page->layout(); if (lay && lay->count() == 1) { returnItem = lay->itemAt(0); } else { returnItem = lay; } return returnItem; } void TabBar::setTabText(int index, const QString &label) { if (index >= d->pages.count()) { return; } d->tabProxy->native->setTabText(index, label); } QString TabBar::tabText(int index) const { return d->tabProxy->native->tabText(index); } void TabBar::setTabIcon(int index, const QIcon &icon) { d->tabProxy->native->setTabIcon(index, icon); } QIcon TabBar::tabIcon(int index) const { return d->tabProxy->native->tabIcon(index); } void TabBar::setTabBarShown(bool show) { if (!show && !d->tabWidgetMode) { return; } if (d->tabBarShown == show) { return; } d->tabBarShown = show; if (!show) { d->tabProxy->hide(); d->tabWidgetLayout->removeItem(d->tabBarLayout); } else { d->tabProxy->show(); d->tabWidgetLayout->insertItem(0, d->tabBarLayout); } } bool TabBar::isTabBarShown() const { return d->tabBarShown; } void TabBar::setStyleSheet(const QString &stylesheet) { d->tabProxy->native->setStyleSheet(stylesheet); } QString TabBar::styleSheet() const { return d->tabProxy->native->styleSheet(); } void TabBar::setTabHighlighted(int index, bool highlight) { d->tabProxy->native->setTabHighlighted(index, highlight); } bool TabBar::isTabHighlighted(int index) const { return d->tabProxy->native->isTabHighlighted(index); } KTabBar *TabBar::nativeWidget() const { return d->tabProxy->native; } void TabBar::wheelEvent(QGraphicsSceneWheelEvent * event) { Q_UNUSED(event) //Still here for binary compatibility } void TabBar::changeEvent(QEvent *event) { d->changeEvent(event); QGraphicsWidget::changeEvent(event); } void TabBar::setFirstPositionWidget(QGraphicsWidget *widget) { if (d->lastPositionWidget.data() == widget) { return; } if (d->firstPositionWidget) { QGraphicsWidget *widget = d->firstPositionWidget.data(); d->tabBarLayout->removeItem(widget); scene()->removeItem(widget); widget->deleteLater(); } d->firstPositionWidget = widget; if (widget) { widget->setParentItem(this); if (layoutDirection() == Qt::LeftToRight) { d->tabBarLayout->insertItem(0, widget); } else { d->tabBarLayout->addItem(widget); } } } QGraphicsWidget *TabBar::firstPositionWidget() const { return d->firstPositionWidget.data(); } void TabBar::setLastPositionWidget(QGraphicsWidget *widget) { if (d->lastPositionWidget.data() == widget) { return; } if (d->lastPositionWidget) { QGraphicsWidget *widget = d->lastPositionWidget.data(); d->tabBarLayout->removeItem(widget); scene()->removeItem(widget); widget->deleteLater(); } d->lastPositionWidget = widget; if (widget) { widget->setParentItem(this); if (layoutDirection() == Qt::LeftToRight) { d->tabBarLayout->addItem(widget); } else { d->tabBarLayout->insertItem(0, widget); } } } QGraphicsWidget *TabBar::lastPositionWidget() const { return d->lastPositionWidget.data(); } } // namespace Plasma #include "moc_tabbar.cpp"