summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2014-11-25 14:07:29 (GMT)
committerKevin Smith <git@kismith.co.uk>2014-12-14 14:44:00 (GMT)
commitb4a54583c4d575fe152122c21da616c3c942bbfd (patch)
tree7e558fd9cb75291f4d5eec90da9d8dd5650ceea2 /Swift/QtUI/Trellis
parent92672e17a52d0c86e693183627c8c6b8fa44fb86 (diff)
downloadswift-b4a54583c4d575fe152122c21da616c3c942bbfd.zip
swift-b4a54583c4d575fe152122c21da616c3c942bbfd.tar.bz2
Add support for new trellis layout for chat windows.
This includes dynamic customizable grid layouting of chat tabs including: - arrengement of tabs via menu, keyboard shortcuts or drag'n'drop - change of grid size via mouse or keyboard - save/restore of grid size and positions of chats inside the grid Test-Information: Tested on OS X 10.9.8. Change-Id: I43f94293a1c672971640c3000abfc6530f2ea7a8
Diffstat (limited to 'Swift/QtUI/Trellis')
-rw-r--r--Swift/QtUI/Trellis/QtDNDTabBar.cpp151
-rw-r--r--Swift/QtUI/Trellis/QtDNDTabBar.h40
-rw-r--r--Swift/QtUI/Trellis/QtDynamicGridLayout.cpp431
-rw-r--r--Swift/QtUI/Trellis/QtDynamicGridLayout.h74
-rw-r--r--Swift/QtUI/Trellis/QtGridSelectionDialog.cpp153
-rw-r--r--Swift/QtUI/Trellis/QtGridSelectionDialog.h57
6 files changed, 906 insertions, 0 deletions
diff --git a/Swift/QtUI/Trellis/QtDNDTabBar.cpp b/Swift/QtUI/Trellis/QtDNDTabBar.cpp
new file mode 100644
index 0000000..af9c0f5
--- /dev/null
+++ b/Swift/QtUI/Trellis/QtDNDTabBar.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/Trellis/QtDNDTabBar.h>
+
+#include <cassert>
+
+#include <QMouseEvent>
+#include <QDropEvent>
+#include <QDrag>
+#include <QMimeData>
+#include <QPainter>
+#include <QTabWidget>
+
+namespace Swift {
+
+QtDNDTabBar::QtDNDTabBar(QWidget* parent) : QTabBar(parent) {
+ setAcceptDrops(true);
+
+ // detect the default tab bar height;
+ insertTab(0, "");
+ defaultTabHeight = QTabBar::sizeHint().height();
+ removeTab(0);
+}
+
+QtDNDTabBar::~QtDNDTabBar() {
+
+}
+
+int QtDNDTabBar::getDragIndex() const {
+ return dragIndex;
+}
+
+QString QtDNDTabBar::getDragText() const {
+ return dragText;
+}
+
+QWidget* QtDNDTabBar::getDragWidget() const {
+ return dragWidget;
+}
+
+QSize QtDNDTabBar::sizeHint() const {
+ QSize hint = QTabBar::sizeHint();
+ if (hint.isEmpty()) {
+ hint = QSize(parentWidget()->width(), defaultTabHeight);
+ }
+ return hint;
+}
+
+void QtDNDTabBar::dragEnterEvent(QDragEnterEvent* dragEnterEvent) {
+ QtDNDTabBar* sourceTabBar = dynamic_cast<QtDNDTabBar*>(dragEnterEvent->source());
+ if (sourceTabBar) {
+ dragEnterEvent->acceptProposedAction();
+ }
+}
+
+void QtDNDTabBar::dropEvent(QDropEvent* dropEvent) {
+ QtDNDTabBar* sourceTabBar = dynamic_cast<QtDNDTabBar*>(dropEvent->source());
+ if (sourceTabBar && dropEvent->mimeData() && dropEvent->mimeData()->data("action") == QByteArray("application/tab-detach")) {
+ QtDNDTabBar* source = dynamic_cast<QtDNDTabBar*>(dropEvent->source());
+
+ int targetTabIndex = tabAt(dropEvent->pos());
+ QRect rect = tabRect(targetTabIndex);
+ if (targetTabIndex >= 0 && (dropEvent->pos().x() - rect.left() - rect.width()/2 > 0)) {
+ targetTabIndex++;
+ }
+
+ QWidget* tab = source->getDragWidget();
+ assert(tab);
+ QTabWidget* targetTabWidget = dynamic_cast<QTabWidget*>(parentWidget());
+
+ QString tabText = source->getDragText();
+
+ /*
+ * When you add a widget to an empty QTabWidget, it's automatically made the current widget.
+ * Making the widget the current widget, widget->show() is called for the widget. Directly reacting
+ * to that event, and adding the widget again to the QTabWidget results in undefined behavior. For
+ * example the tab label is shown but the widget is neither has the old nor in the new QTabWidget as
+ * parent. Blocking signals on the QWidget to be added to a QTabWidget prevents this behavior.
+ */
+ targetTabWidget->setUpdatesEnabled(false);
+ tab->blockSignals(true);
+ targetTabWidget->insertTab(targetTabIndex, tab, tabText);
+ dropEvent->acceptProposedAction();
+ tab->blockSignals(false);
+ targetTabWidget->setUpdatesEnabled(true);
+ onDropSucceeded();
+ }
+}
+
+bool QtDNDTabBar::event(QEvent* event) {
+ QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event);
+ if (mouseEvent) {
+ QWidget* childAtPoint = window()->childAt(mapTo(window(), mouseEvent->pos()));
+ QtDNDTabBar* underMouse = dynamic_cast<QtDNDTabBar*>(childAtPoint);
+ if (!underMouse && childAtPoint) {
+ underMouse = dynamic_cast<QtDNDTabBar*>(childAtPoint->parent());
+ }
+ if (!underMouse && currentIndex() >= 0) {
+ // detach and drag
+
+ // stop move event
+ QMouseEvent* finishMoveEvent = new QMouseEvent (QEvent::MouseMove, mouseEvent->pos (), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
+ QTabBar::event(finishMoveEvent);
+ delete finishMoveEvent;
+ finishMoveEvent = NULL;
+
+ // start drag
+ QDrag* drag = new QDrag(this);
+ QMimeData* mimeData = new QMimeData;
+
+ // distinguish tab-reordering drops from other ones
+ mimeData->setData("action", "application/tab-detach") ;
+ drag->setMimeData(mimeData);
+
+ // set drag image
+ QRect rect = tabRect( currentIndex() );
+#if QT_VERSION >= 0x050000
+ QPixmap pixmap = grab(rect);
+#else
+ QPixmap pixmap = QPixmap::grabWidget(this, rect);
+#endif
+ QPixmap targetPixmap (pixmap.size ());
+ QPainter painter (&targetPixmap);
+ painter.setOpacity(0.9);
+ painter.drawPixmap(0,0, pixmap);
+ painter.end ();
+ drag->setPixmap (targetPixmap);
+
+ drag->setHotSpot(QPoint(drag->pixmap().width()/2, drag->pixmap().height()));
+
+ dragIndex = currentIndex();
+ dragText = tabText(dragIndex);
+ dragWidget = dynamic_cast<QTabWidget*>(parent())->widget(dragIndex);
+ assert(dragWidget);
+ dynamic_cast<QTabWidget*>(parent())->removeTab(currentIndex());
+ Qt::DropAction dropAction = drag->exec();
+ if (dropAction == Qt::IgnoreAction) {
+ // aborted drag, put tab back in place
+ dynamic_cast<QTabWidget*>(parent())->insertTab(dragIndex, dragWidget, dragText);
+ }
+ return true;
+ }
+ }
+ return QTabBar::event(event);
+}
+
+}
diff --git a/Swift/QtUI/Trellis/QtDNDTabBar.h b/Swift/QtUI/Trellis/QtDNDTabBar.h
new file mode 100644
index 0000000..4f26bc1
--- /dev/null
+++ b/Swift/QtUI/Trellis/QtDNDTabBar.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QTabBar>
+
+namespace Swift {
+
+class QtDNDTabBar : public QTabBar {
+ Q_OBJECT
+ public:
+ explicit QtDNDTabBar(QWidget* parent = 0);
+ virtual ~QtDNDTabBar();
+
+ int getDragIndex() const;
+ QString getDragText() const;
+ QWidget* getDragWidget() const;
+
+ virtual QSize sizeHint() const;
+
+ signals:
+ void onDropSucceeded();
+
+ protected:
+ void dragEnterEvent(QDragEnterEvent* dragEnterEvent);
+ void dropEvent(QDropEvent* dropEvent);
+ bool event(QEvent* event);
+
+ private:
+ int defaultTabHeight;
+ int dragIndex;
+ QString dragText;
+ QWidget* dragWidget;
+};
+
+}
diff --git a/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp b/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp
new file mode 100644
index 0000000..3b33dd3
--- /dev/null
+++ b/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/Trellis/QtDynamicGridLayout.h>
+
+#include <cassert>
+
+#include <QLayoutItem>
+#include <QGridLayout>
+#include <QtDebug>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtTabbable.h>
+#include <Swift/QtUI/QtTabWidget.h>
+#include <Swift/QtUI/Trellis/QtDNDTabBar.h>
+
+namespace Swift {
+
+QtDynamicGridLayout::QtDynamicGridLayout(QWidget* parent, bool enableDND) : QWidget(parent), dndEnabled_(enableDND) {
+ gridLayout_ = new QGridLayout(this);
+ setContentsMargins(0,0,0,0);
+ setDimensions(QSize(1,1));
+}
+
+QtDynamicGridLayout::~QtDynamicGridLayout() {
+}
+
+int QtDynamicGridLayout::addTab(QtTabbable* tab, const QString& title) {
+ assert(gridLayout_->rowCount() > 0 && gridLayout_->columnCount() > 0);
+ int index = -1;
+
+ QPoint lastPos(0,0);
+ if (tabPositions_.contains(P2QSTRING(tab->getID()))) {
+ lastPos = tabPositions_[P2QSTRING(tab->getID())];
+ }
+
+ lastPos = QPoint(qMin(lastPos.x(), gridLayout_->columnCount() - 1), qMin(lastPos.y(), gridLayout_->rowCount() - 1));
+
+ QLayoutItem* item = gridLayout_->itemAtPosition(lastPos.y(), lastPos.x());
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(item ? item->widget() : 0);
+ if (tabWidget) {
+ index = tabWidget->addTab(tab, title);
+ }
+ return tabWidget ? indexOf(tab) : -1;
+}
+
+int QtDynamicGridLayout::count() const {
+ int count = 0;
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ if (tabWidget) {
+ count += tabWidget->count();
+ }
+ }
+ }
+ return count;
+}
+
+QWidget* QtDynamicGridLayout::widget(int index) const {
+ QWidget* widgetAtIndex = NULL;
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ if (tabWidget) {
+ if (index < tabWidget->count()) {
+ widgetAtIndex = tabWidget->widget(index);
+ return widgetAtIndex;
+ }
+ else {
+ index -= tabWidget->count();
+ }
+ }
+ }
+ }
+ return widgetAtIndex;
+}
+
+int QtDynamicGridLayout::indexOf(const QWidget* widget) const {
+ int index = 0;
+ if (widget) {
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ if (tabWidget) {
+ for (int n = 0; n < tabWidget->count(); n++) {
+ QWidget* nthWidget = tabWidget->widget(n);
+ if (nthWidget == widget) {
+ return index;
+ }
+ index++;
+ }
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+int QtDynamicGridLayout::currentIndex() const {
+ return indexOf(currentWidget());
+}
+
+void QtDynamicGridLayout::setCurrentIndex(int index) {
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
+ if (tabIndex >= 0) {
+ tabWidget->setCurrentIndex(tabIndex);
+ if (!tabWidget->hasFocus()) {
+ tabWidget->widget(tabIndex)->setFocus(Qt::TabFocusReason);
+ }
+ } else {
+ assert(false);
+ }
+}
+
+void QtDynamicGridLayout::removeTab(int index) {
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
+ if (tabWidget) {
+ tabWidget->removeTab(tabIndex);
+ }
+}
+
+QWidget* QtDynamicGridLayout::currentWidget() const {
+ QWidget* current = NULL;
+ current = focusWidget();
+ while (current && !dynamic_cast<QtTabbable*>(current)) {
+ if (current->parentWidget()) {
+ current = current->parentWidget();
+ } else {
+ current = NULL;
+ break;
+ }
+ }
+ if (!current) {
+ current = widget(0);
+ }
+ return current;
+}
+
+void QtDynamicGridLayout::setCurrentWidget(QWidget* widget) {
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ if (tabWidget) {
+ for (int n = 0; n < tabWidget->count(); n++) {
+ if (tabWidget->widget(n) == widget) {
+ tabWidget->setCurrentWidget(widget);
+ }
+ }
+ }
+ }
+ }
+}
+
+QtTabWidget* QtDynamicGridLayout::indexToTabWidget(int index, int& tabIndex) {
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ if (tabWidget) {
+ if (index < tabWidget->count()) {
+ tabIndex = index;
+ return tabWidget;
+ }
+ else {
+ index -= tabWidget->count();
+ if (index < 0) {
+ qWarning() << "Called QtDynamicGridLayout::setCurrentIndex with index out of bounds: index = " << index;
+ tabIndex = -1;
+ return NULL;
+ }
+ }
+ }
+ }
+ }
+ tabIndex = -1;
+ return NULL;
+}
+
+bool QtDynamicGridLayout::isDNDEnabled() const {
+ return dndEnabled_;
+}
+
+QHash<QString, QPoint> QtDynamicGridLayout::getTabPositions() const {
+ return tabPositions_;
+}
+
+void QtDynamicGridLayout::setTabPositions(const QHash<QString, QPoint> positions) {
+ tabPositions_ = positions;
+}
+
+QSize QtDynamicGridLayout::getDimension() const {
+ return QSize(gridLayout_->columnCount(), gridLayout_->rowCount());
+}
+
+void QtDynamicGridLayout::setDimensions(const QSize& dim) {
+ assert(dim.width() > 0 && dim.height() > 0);
+ setUpdatesEnabled(false);
+
+ QGridLayout* oldLayout = dynamic_cast<QGridLayout*>(layout());
+ QGridLayout* newLayout = new QGridLayout;
+ newLayout->setSpacing(4);
+ newLayout->setContentsMargins(0,0,0,0);
+
+ int oldWidth = oldLayout->columnCount();
+ int oldHeight = oldLayout->rowCount();
+ int maxCol = qMax(oldWidth, dim.width());
+ int minCol = qMin(oldWidth, dim.width());
+ int maxRow = qMax(oldHeight, dim.height());
+ int minRow = qMin(oldHeight, dim.height());
+
+ for (int row = 0; row < maxRow; row++) {
+ for (int col = 0; col < maxCol; col++) {
+ QLayoutItem* oldItem = oldLayout->itemAtPosition(row, col);
+ QLayoutItem* newItem = newLayout->itemAtPosition(row, col);
+ bool removeRow = !(row < dim.height());
+ bool removeCol = !(col < dim.width());
+
+ if (removeCol || removeRow) {
+ if (oldItem) {
+ int squeezeRow = removeRow ? (minRow - 1) : row;
+ int squeezeCol = removeCol ? (minCol - 1) : col;
+ newItem = newLayout->itemAtPosition(squeezeRow, squeezeCol);
+ if (!newItem) {
+ newLayout->addWidget(createDNDTabWidget(this), squeezeRow, squeezeCol);
+ newItem = newLayout->itemAtPosition(squeezeRow, squeezeCol);
+ }
+ QtTabWidget* oldTabWidget = dynamic_cast<QtTabWidget*>(oldItem->widget());
+ QtTabWidget* newTabWidget = dynamic_cast<QtTabWidget*>(newItem->widget());
+ assert(oldTabWidget && newTabWidget);
+
+ oldTabWidget->hide();
+ while(oldTabWidget->count()) {
+ QIcon icon = oldTabWidget->tabIcon(0);
+ QString text = oldTabWidget->tabText(0);
+ newTabWidget->addTab(oldTabWidget->widget(0), icon, text);
+ }
+ delete oldTabWidget;
+ }
+ } else {
+ if (oldItem) {
+ newLayout->addWidget(oldItem->widget(), row, col);
+ newItem = newLayout->itemAtPosition(row, col);
+ } else {
+ newLayout->addWidget(createDNDTabWidget(this), row, col);
+ }
+ }
+ }
+ }
+
+ for (int col = 0; col < dim.width(); col++) {
+ newLayout->setColumnStretch(col, 1);
+ }
+ for (int row = 0; row < dim.height(); row++) {
+ newLayout->setRowStretch(row, 1);
+ }
+
+ setUpdatesEnabled(true);
+ delete layout();
+ setLayout(newLayout);
+ gridLayout_ = newLayout;
+}
+
+void QtDynamicGridLayout::moveCurrentTabRight() {
+ int index = currentIndex();
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
+ assert(tabWidget);
+ int newTabIndex = (tabIndex + 1) % tabWidget->count();
+ moveTab(tabWidget, tabIndex, newTabIndex);
+}
+
+void QtDynamicGridLayout::moveCurrentTabLeft() {
+ int index = currentIndex();
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
+ assert(tabWidget);
+ int newTabIndex = (tabWidget->count() + tabIndex - 1) % tabWidget->count();
+ moveTab(tabWidget, tabIndex, newTabIndex);
+}
+
+void QtDynamicGridLayout::moveCurrentTabToNextGroup() {
+ int index = currentIndex();
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
+
+ int row = -1;
+ int col = -1;
+ int tmp;
+ gridLayout_->getItemPosition(gridLayout_->indexOf(tabWidget), &row, &col, &tmp, &tmp);
+
+ // calculate next cell
+ col++;
+ if (!(col < gridLayout_->columnCount())) {
+ col = 0;
+ row++;
+ if (!(row < gridLayout_->rowCount())) {
+ row = 0;
+ }
+ }
+
+ QtTabWidget* targetTabWidget = dynamic_cast<QtTabWidget*>(gridLayout_->itemAtPosition(row, col)->widget());
+ assert(tabWidget);
+ assert(targetTabWidget);
+
+ // fetch tab information
+ QWidget* tab = tabWidget->widget(tabIndex);
+ QString tabText = tabWidget->tabText(tabIndex);
+
+ // move tab
+ tab->blockSignals(true);
+ targetTabWidget->addTab(tab, tabText);
+ tab->blockSignals(false);
+ tab->setFocus(Qt::TabFocusReason);
+
+ updateTabPositions();
+}
+
+void QtDynamicGridLayout::moveCurrentTabToPreviousGroup() {
+ int index = currentIndex();
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex);
+
+ int row = -1;
+ int col = -1;
+ int tmp;
+ gridLayout_->getItemPosition(gridLayout_->indexOf(tabWidget), &row, &col, &tmp, &tmp);
+
+ // calculate next cell
+ col--;
+ if (col < 0) {
+ col = gridLayout_->columnCount() - 1;
+ row--;
+ if (row < 0) {
+ row = gridLayout_->rowCount() - 1;
+ }
+ }
+
+ QtTabWidget* targetTabWidget = dynamic_cast<QtTabWidget*>(gridLayout_->itemAtPosition(row, col)->widget());
+ assert(tabWidget);
+ assert(targetTabWidget);
+
+ // fetch tab information
+ QWidget* tab = tabWidget->widget(tabIndex);
+ QString tabText = tabWidget->tabText(tabIndex);
+
+ // move tab
+ tab->blockSignals(true);
+ targetTabWidget->addTab(tab, tabText);
+ tab->blockSignals(false);
+ tab->setFocus(Qt::TabFocusReason);
+
+ updateTabPositions();
+}
+
+void QtDynamicGridLayout::handleTabCloseRequested(int index) {
+ updateTabPositions();
+ QtTabWidget* tabWidgetSender = dynamic_cast<QtTabWidget*>(sender());
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ if (tabWidget == tabWidgetSender) {
+ tabCloseRequested(index);
+ }
+ else {
+ index += tabWidget->count();
+ }
+ }
+ }
+}
+
+void QtDynamicGridLayout::updateTabPositions() {
+ for (int y = 0; y < gridLayout_->rowCount(); y++) {
+ for (int x = 0; x < gridLayout_->columnCount(); x++) {
+ QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x);
+ QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget());
+ assert(tabWidget);
+ for (int index = 0; index < tabWidget->count(); index++) {
+ QtTabbable* tab = dynamic_cast<QtTabbable*>(tabWidget->widget(index));
+ assert(tab);
+ tabPositions_.insert(P2QSTRING(tab->getID()), QPoint(x, y));
+ }
+ }
+ }
+}
+
+void QtDynamicGridLayout::moveTab(QtTabWidget* tabWidget, int oldIndex, int newIndex) {
+ tabWidget->widget(oldIndex)->blockSignals(true);
+ tabWidget->widget(newIndex)->blockSignals(true);
+#if QT_VERSION >= 0x040500
+ tabWidget->tabBar()->moveTab(oldIndex, newIndex);
+#else
+#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled.
+ tabWidget->widget(oldIndex)->blockSignals(false);
+ tabWidget->widget(newIndex)->blockSignals(false);
+#endif
+}
+
+QtTabWidget* QtDynamicGridLayout::createDNDTabWidget(QWidget* parent) {
+ QtTabWidget* tab = new QtTabWidget(parent);
+ if (dndEnabled_) {
+ QtDNDTabBar* tabBar = new QtDNDTabBar(tab);
+ connect(tabBar, SIGNAL(onDropSucceeded()), this, SLOT(updateTabPositions()));
+ tab->setTabBar(tabBar);
+ }
+ tab->setUsesScrollButtons(true);
+ tab->setElideMode(Qt::ElideRight);
+#if QT_VERSION >= 0x040500
+ /*For Macs, change the tab rendering.*/
+ tab->setDocumentMode(true);
+ /*Closable tabs are only in Qt4.5 and later*/
+ tab->setTabsClosable(true);
+ tab->setMovable(true);
+ connect(tab, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int)));
+#else
+#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled.
+#endif
+ return tab;
+}
+
+}
diff --git a/Swift/QtUI/Trellis/QtDynamicGridLayout.h b/Swift/QtUI/Trellis/QtDynamicGridLayout.h
new file mode 100644
index 0000000..c87dc21
--- /dev/null
+++ b/Swift/QtUI/Trellis/QtDynamicGridLayout.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QGridLayout>
+#include <QHash>
+#include <QPoint>
+#include <QSize>
+#include <QString>
+#include <QWidget>
+
+namespace Swift {
+ class QtTabbable;
+ class QtTabWidget;
+
+ class QtDynamicGridLayout : public QWidget {
+ Q_OBJECT
+ public:
+ explicit QtDynamicGridLayout(QWidget* parent = 0, bool enableDND = false);
+ virtual ~QtDynamicGridLayout();
+
+ QSize getDimension() const;
+
+ // emulate QtTabWidget API
+ int addTab(QtTabbable* tab, const QString& title);
+ void removeTab(int index);
+ int count() const;
+
+ QWidget* widget(int index) const;
+ QWidget* currentWidget() const;
+ void setCurrentWidget(QWidget* widget);
+
+ QtTabWidget* indexToTabWidget(int index, int& tabIndex);
+
+ int indexOf(const QWidget* widget) const;
+ int currentIndex() const;
+ void setCurrentIndex(int index);
+
+ bool isDNDEnabled() const;
+
+ QHash<QString, QPoint> getTabPositions() const;
+ void setTabPositions(const QHash<QString, QPoint> positions);
+
+ signals:
+ void tabCloseRequested(int index);
+
+ public slots:
+ void setDimensions(const QSize& dim);
+
+ // Tab Management
+ void moveCurrentTabRight();
+ void moveCurrentTabLeft();
+ void moveCurrentTabToNextGroup();
+ void moveCurrentTabToPreviousGroup();
+
+ void updateTabPositions();
+
+ private slots:
+ void handleTabCloseRequested(int index);
+
+ private:
+ void moveTab(QtTabWidget* tabWidget, int oldIndex, int newIndex);
+ QtTabWidget* createDNDTabWidget(QWidget* parent);
+
+ private:
+ QGridLayout *gridLayout_;
+ bool dndEnabled_;
+ QHash<QString, QPoint> tabPositions_;
+ };
+}
diff --git a/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp b/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp
new file mode 100644
index 0000000..a7d3672
--- /dev/null
+++ b/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/Trellis/QtGridSelectionDialog.h>
+
+#include <QStyle>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QStyleOptionFrame>
+#include <QStyleOptionMenuItem>
+
+namespace Swift {
+
+QtGridSelectionDialog::QtGridSelectionDialog(QWidget* parent) : QWidget(parent)
+{
+ frameSize = QSize(23,23) * 2;
+ maxGridSize = QSize(7,7);
+ minGridSize = QSize(1,1);
+ currentGridSize = QSize(1,1);
+ padding = 4;
+ setWindowFlags(Qt::Popup);
+ horizontalMargin = style()->pixelMetric(QStyle::PM_MenuVMargin);
+ verticalMargin = style()->pixelMetric(QStyle::PM_MenuVMargin);
+
+}
+
+QSize QtGridSelectionDialog::sizeHint() const
+{
+ // PM_MenuVMargin | frameSize | ( padding | frameSize ) * | PM_MenuVMargin
+ int width = horizontalMargin + frameSize.width() + (padding + frameSize.width()) * (currentGridSize.width() - 1) + horizontalMargin;
+ int height = verticalMargin + frameSize.height() + (padding + frameSize.height()) * (currentGridSize.height() - 1) + verticalMargin;
+ return QSize(width, height);
+}
+
+void QtGridSelectionDialog::setCurrentGridSize(const QSize& size) {
+ currentGridSize = size;
+ emit currentGridSizeChanged(size);
+}
+
+QSize QtGridSelectionDialog::getCurrentGridSize() const {
+ return currentGridSize;
+}
+
+void QtGridSelectionDialog::setMinGridSize(const QSize& size) {
+ minGridSize = size;
+ emit minGridSizeChanged(size);
+}
+
+QSize QtGridSelectionDialog::getMinGridSize() const {
+ return minGridSize;
+}
+
+void QtGridSelectionDialog::setMaxGridSize(const QSize& size) {
+ maxGridSize = size;
+ emit maxGridSizeChanged(size);
+}
+
+QSize QtGridSelectionDialog::getMaxGridSize() const {
+ return maxGridSize;
+}
+
+void QtGridSelectionDialog::keyReleaseEvent(QKeyEvent* event) {
+ if (event) {
+ QSize newGridSize = currentGridSize;
+ if (event->key() == Qt::Key_Up) {
+ newGridSize += QSize(0, -1);
+ }
+ else if (event->key() == Qt::Key_Down) {
+ newGridSize += QSize(0, 1);
+ }
+ else if (event->key() == Qt::Key_Left) {
+ newGridSize += QSize(-1, 0);
+ }
+ else if (event->key() == Qt::Key_Right) {
+ newGridSize += QSize(1, 0);
+ }
+ else if (event->key() == Qt::Key_Return) {
+ hide();
+ setCurrentGridSize(currentGridSize);
+ }
+ if (minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize) != currentGridSize) {
+ currentGridSize = minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize);
+ resize(sizeHint());
+ }
+ }
+}
+
+void QtGridSelectionDialog::mousePressEvent(QMouseEvent*) {
+ hide();
+ setCurrentGridSize(currentGridSize);
+}
+
+void QtGridSelectionDialog::paintEvent(QPaintEvent*) {
+ QPainter painter(this);
+ QStyleOptionMenuItem option;
+ option.state = QStyle::State_Enabled | QStyle::State_Selected;
+ option.menuRect = QRect(QPoint(0,0), frameSize);
+ for (int x = 0; x < currentGridSize.width(); x++) {
+ for (int y = 0; y < currentGridSize.height(); y++) {
+ int xPos = horizontalMargin + (x * (frameSize.width() + padding));
+ int yPos = verticalMargin + (y * (frameSize.height() + padding));
+ option.menuRect.moveTo(QPoint(xPos, yPos));
+ option.rect = option.menuRect;
+ style()->drawControl(QStyle::CE_MenuItem, &option, &painter, 0);
+ }
+ }
+}
+
+void QtGridSelectionDialog::showEvent(QShowEvent*) {
+ int xPos = horizontalMargin + frameSize.width() + (padding + frameSize.width()) * (currentGridSize.width() - 1) - frameSize.width()/2;
+ int yPos = verticalMargin + frameSize.height() + (padding + frameSize.height()) * (currentGridSize.height() - 1) - frameSize.height()/2;
+ QCursor::setPos(mapToGlobal(QPoint(xPos, yPos)));
+ setMouseTracking(true);
+}
+
+void QtGridSelectionDialog::hideEvent(QHideEvent*) {
+ setMouseTracking(false);
+}
+
+void QtGridSelectionDialog::mouseMoveEvent(QMouseEvent*) {
+ QPoint diff = (frameGeometry().bottomRight() - QCursor::pos());
+ QSize newDimensions = currentGridSize;
+ if (diff.x() > frameSize.width() * 1.5) {
+ newDimensions -= QSize(diff.x() / (frameSize.width() * 1.5), 0);
+ }
+ if (diff.y() > frameSize.height() * 1.5) {
+ newDimensions -= QSize(0, diff.y() / (frameSize.height() * 1.5));
+ }
+ if (minGridSize.expandedTo(newDimensions).boundedTo(maxGridSize) != currentGridSize) {
+ currentGridSize = minGridSize.expandedTo(newDimensions).boundedTo(maxGridSize);
+ resize(sizeHint());
+ }
+}
+
+void QtGridSelectionDialog::leaveEvent(QEvent *) {
+ QPoint diff = (frameGeometry().bottomRight() - QCursor::pos());
+ QSize newGridSize = currentGridSize;
+ if (diff.x() < 0) {
+ newGridSize += QSize(1,0);
+ }
+ if (diff.y() < 0) {
+ newGridSize += QSize(0,1);
+ }
+ if (minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize) != currentGridSize) {
+ currentGridSize = minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize);
+ resize(sizeHint());
+ }
+}
+
+}
diff --git a/Swift/QtUI/Trellis/QtGridSelectionDialog.h b/Swift/QtUI/Trellis/QtGridSelectionDialog.h
new file mode 100644
index 0000000..430189a
--- /dev/null
+++ b/Swift/QtUI/Trellis/QtGridSelectionDialog.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2014 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QSize>
+#include <QWidget>
+
+namespace Swift {
+
+ class QtGridSelectionDialog : public QWidget {
+ Q_OBJECT
+
+ Q_PROPERTY(QSize currentGridSize READ getCurrentGridSize WRITE setCurrentGridSize NOTIFY currentGridSizeChanged)
+ Q_PROPERTY(QSize minGridSize READ getMinGridSize WRITE setMinGridSize NOTIFY minGridSizeChanged)
+ Q_PROPERTY(QSize maxGridSize READ getMaxGridSize WRITE setMaxGridSize NOTIFY maxGridSizeChanged)
+ public:
+ explicit QtGridSelectionDialog(QWidget* parent = 0);
+
+ virtual QSize sizeHint() const;
+
+ void setCurrentGridSize(const QSize& size);
+ QSize getCurrentGridSize() const;
+ void setMinGridSize(const QSize& size);
+ QSize getMinGridSize() const;
+ void setMaxGridSize(const QSize& size);
+ QSize getMaxGridSize() const;
+
+ signals:
+ void currentGridSizeChanged(QSize);
+ void minGridSizeChanged(QSize);
+ void maxGridSizeChanged(QSize);
+
+ protected:
+ void keyReleaseEvent(QKeyEvent* event);
+ void mousePressEvent(QMouseEvent* event);
+ void mouseMoveEvent(QMouseEvent* event);
+ void paintEvent(QPaintEvent* event);
+ void showEvent(QShowEvent* event);
+ void hideEvent(QHideEvent* event);
+ void leaveEvent(QEvent *event);
+
+ private:
+ int padding;
+ int horizontalMargin;
+ int verticalMargin;
+
+ QSize frameSize;
+
+ QSize currentGridSize;
+ QSize minGridSize;
+ QSize maxGridSize;
+ };
+}