diff options
author | Tobias Markmann <tm@ayena.de> | 2014-11-25 14:07:29 (GMT) |
---|---|---|
committer | Kevin Smith <git@kismith.co.uk> | 2014-12-14 14:44:00 (GMT) |
commit | b4a54583c4d575fe152122c21da616c3c942bbfd (patch) | |
tree | 7e558fd9cb75291f4d5eec90da9d8dd5650ceea2 /Swift/QtUI/Trellis | |
parent | 92672e17a52d0c86e693183627c8c6b8fa44fb86 (diff) | |
download | swift-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.cpp | 151 | ||||
-rw-r--r-- | Swift/QtUI/Trellis/QtDNDTabBar.h | 40 | ||||
-rw-r--r-- | Swift/QtUI/Trellis/QtDynamicGridLayout.cpp | 431 | ||||
-rw-r--r-- | Swift/QtUI/Trellis/QtDynamicGridLayout.h | 74 | ||||
-rw-r--r-- | Swift/QtUI/Trellis/QtGridSelectionDialog.cpp | 153 | ||||
-rw-r--r-- | Swift/QtUI/Trellis/QtGridSelectionDialog.h | 57 |
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; + }; +} |