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
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')
-rw-r--r--Swift/QtUI/CocoaApplicationActivateHelper.h2
-rw-r--r--Swift/QtUI/QtChatTabs.cpp201
-rw-r--r--Swift/QtUI/QtChatTabs.h28
-rw-r--r--Swift/QtUI/QtChatWindow.cpp8
-rw-r--r--Swift/QtUI/QtChatWindow.h6
-rw-r--r--Swift/QtUI/QtFileTransferListWidget.cpp4
-rw-r--r--Swift/QtUI/QtFileTransferListWidget.h2
-rw-r--r--Swift/QtUI/QtHistoryWindow.cpp4
-rw-r--r--Swift/QtUI/QtHistoryWindow.h2
-rw-r--r--Swift/QtUI/QtLoginWindow.cpp4
-rw-r--r--Swift/QtUI/QtLoginWindow.h1
-rw-r--r--Swift/QtUI/QtMainWindow.cpp36
-rw-r--r--Swift/QtUI/QtSwift.cpp5
-rw-r--r--Swift/QtUI/QtSwift.h2
-rw-r--r--Swift/QtUI/QtTabWidget.cpp3
-rw-r--r--Swift/QtUI/QtTabWidget.h7
-rw-r--r--Swift/QtUI/QtTabbable.cpp9
-rw-r--r--Swift/QtUI/QtTabbable.h10
-rw-r--r--Swift/QtUI/QtUIFactory.cpp1
-rw-r--r--Swift/QtUI/QtXMLConsoleWidget.cpp4
-rw-r--r--Swift/QtUI/QtXMLConsoleWidget.h2
-rw-r--r--Swift/QtUI/SConscript5
-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
28 files changed, 1160 insertions, 92 deletions
diff --git a/Swift/QtUI/CocoaApplicationActivateHelper.h b/Swift/QtUI/CocoaApplicationActivateHelper.h
index 8bc2af5..8ea86e0 100644
--- a/Swift/QtUI/CocoaApplicationActivateHelper.h
+++ b/Swift/QtUI/CocoaApplicationActivateHelper.h
@@ -16,7 +16,7 @@ namespace Swift {
class CocoaApplicationActivateHelper : public QObject {
public:
CocoaApplicationActivateHelper();
- ~CocoaApplicationActivateHelper();
+ virtual ~CocoaApplicationActivateHelper();
private:
bool eventFilter(QObject* o, QEvent* e);
diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp
index cf335e1..d04977e 100644
--- a/Swift/QtUI/QtChatTabs.cpp
+++ b/Swift/QtUI/QtChatTabs.cpp
@@ -1,72 +1,141 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "QtChatTabs.h"
+#include <Swift/QtUI/QtChatTabs.h>
#include <algorithm>
#include <vector>
+#include <Swiften/Base/Log.h>
+
#include <Swift/Controllers/ChatMessageSummarizer.h>
+#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtTabWidget.h>
+#include <Swift/QtUI/QtTabbable.h>
+#include <Swift/QtUI/Trellis/QtDynamicGridLayout.h>
+#include <Swift/QtUI/Trellis/QtGridSelectionDialog.h>
+#include <QAction>
+#include <QApplication>
#include <QCloseEvent>
+#include <QCursor>
#include <QDesktopWidget>
-#include <QtGlobal>
-#include <QTabWidget>
#include <QLayout>
+#include <QMenu>
#include <QTabBar>
-#include <QApplication>
-#include <qdebug.h>
+#include <QTabWidget>
+#include <QtGlobal>
namespace Swift {
-QtChatTabs::QtChatTabs(bool singleWindow) : QWidget(), singleWindow_(singleWindow) {
+QtChatTabs::QtChatTabs(bool singleWindow, SettingsProvider* settingsProvider, bool trellisMode) : QWidget(), singleWindow_(singleWindow), settingsProvider_(settingsProvider), trellisMode_(trellisMode), dynamicGrid_(NULL), gridSelectionDialog_(NULL) {
#ifndef Q_OS_MAC
setWindowIcon(QIcon(":/logo-chat-16.png"));
#else
setAttribute(Qt::WA_ShowWithoutActivating);
#endif
+ dynamicGrid_ = new QtDynamicGridLayout(this, trellisMode);
+ connect(dynamicGrid_, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int)));
- tabs_ = new QtTabWidget(this);
- tabs_->setUsesScrollButtons(true);
- tabs_->setElideMode(Qt::ElideRight);
-#if QT_VERSION >= 0x040500
- /*For Macs, change the tab rendering.*/
- tabs_->setDocumentMode(true);
- /*Closable tabs are only in Qt4.5 and later*/
- tabs_->setTabsClosable(true);
- tabs_->setMovable(true);
- connect(tabs_, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int)));
-#else
-#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled.
-#endif
QVBoxLayout *layout = new QVBoxLayout;
layout->setSpacing(0);
- layout->setContentsMargins(0, 3, 0, 0);
- layout->addWidget(tabs_);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(dynamicGrid_);
setLayout(layout);
+
+ if (trellisMode) {
+ // restore size
+ std::string gridSizeString = settingsProvider->getSetting(SettingConstants::TRELLIS_GRID_SIZE);
+ if (!gridSizeString.empty()) {
+ QByteArray gridSizeData = QByteArray::fromBase64(P2QSTRING(gridSizeString).toUtf8());
+ QDataStream dataStreamGridSize(&gridSizeData, QIODevice::ReadWrite);
+ QSize gridSize(1,1);
+ dataStreamGridSize >> gridSize;
+ dynamicGrid_->setDimensions(gridSize);
+ }
+
+ // restore positions
+ std::string tabPositionsString = settingsProvider->getSetting(SettingConstants::TRELLIS_GRID_POSITIONS);
+ if (!tabPositionsString.empty()) {
+ QByteArray tabPositionsData = QByteArray::fromBase64(P2QSTRING(tabPositionsString).toUtf8());
+ QDataStream inTabPositions(&tabPositionsData, QIODevice::ReadWrite);
+ QHash<QString, QPoint> tabPositions;
+ inTabPositions >> tabPositions;
+ dynamicGrid_->setTabPositions(tabPositions);
+ }
+ }
+
+ gridSelectionDialog_ = new QtGridSelectionDialog();
+}
+
+QtChatTabs::~QtChatTabs() {
+ if (trellisMode_) {
+ storeTabPositions();
+ }
+ delete gridSelectionDialog_;
}
void QtChatTabs::closeEvent(QCloseEvent* event) {
//Hide first to prevent flickering as each tab is removed.
hide();
- for (int i = tabs_->count() - 1; i >= 0; i--) {
- tabs_->widget(i)->close();
+ if (trellisMode_) {
+ storeTabPositions();
+ }
+
+ for (int i = dynamicGrid_->count() - 1; i >= 0; i--) {
+ dynamicGrid_->widget(i)->close();
}
event->accept();
}
QtTabbable* QtChatTabs::getCurrentTab() {
- return qobject_cast<QtTabbable*>(tabs_->currentWidget());
+ return qobject_cast<QtTabbable*>(dynamicGrid_->currentWidget());
+}
+
+void QtChatTabs::setViewMenu(QMenu* viewMenu) {
+ if (trellisMode_) {
+ viewMenu->addSeparator();
+ QAction* action = new QAction(tr("Change &layout"), this);
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_L));
+ connect(action, SIGNAL(triggered()), this, SLOT(handleOpenLayoutChangeDialog()));
+ viewMenu->addAction(action);
+
+ action = new QAction(tr("Move Tab right"), this);
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Right));
+ connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabRight()));
+ viewMenu->addAction(action);
+
+ action = new QAction(tr("Move Tab left"), this);
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left));
+ connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabLeft()));
+ viewMenu->addAction(action);
+
+ action = new QAction(tr("Move Tab to next group"), this);
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Right));
+ connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabToNextGroup()));
+ viewMenu->addAction(action);
+
+ action = new QAction(tr("Move Tab to previous group"), this);
+ action->setShortcutContext(Qt::ApplicationShortcut);
+ action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Left));
+ connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabToPreviousGroup()));
+ viewMenu->addAction(action);
+ }
}
void QtChatTabs::addTab(QtTabbable* tab) {
QSizePolicy policy = sizePolicy();
/* Chat windows like to grow - don't let them */
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- tabs_->addTab(tab, tab->windowTitle());
+ dynamicGrid_->addTab(tab, tab->windowTitle());
connect(tab, SIGNAL(titleUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection);
connect(tab, SIGNAL(countUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection);
connect(tab, SIGNAL(windowClosing()), this, SLOT(handleTabClosing()), Qt::UniqueConnection);
@@ -85,7 +154,7 @@ void QtChatTabs::handleWidgetShown() {
return;
}
checkForFirstShow();
- if (tabs_->indexOf(widget) >= 0) {
+ if (dynamicGrid_->indexOf(widget) >= 0) {
handleTabTitleUpdated(widget);
return;
}
@@ -101,7 +170,7 @@ void QtChatTabs::handleWantsToActivate() {
setWindowState(windowState() | Qt::WindowActive);
show();
widget->show();
- tabs_->setCurrentWidget(widget);
+ dynamicGrid_->setCurrentWidget(widget);
handleTabTitleUpdated(widget);
widget->setFocus();
raise();
@@ -111,9 +180,9 @@ void QtChatTabs::handleWantsToActivate() {
void QtChatTabs::handleTabClosing() {
QWidget* widget = qobject_cast<QWidget*>(sender());
int index;
- if (widget && ((index = tabs_->indexOf(widget)) >= 0)) {
- tabs_->removeTab(index);
- if (tabs_->count() == 0) {
+ if (widget && ((index = dynamicGrid_->indexOf(widget)) >= 0)) {
+ dynamicGrid_->removeTab(index);
+ if (dynamicGrid_->count() == 0) {
if (!singleWindow_) {
hide();
}
@@ -123,19 +192,19 @@ void QtChatTabs::handleTabClosing() {
}
}
else {
- handleTabTitleUpdated(tabs_->currentWidget());
+ handleTabTitleUpdated(dynamicGrid_->currentWidget());
}
}
}
void QtChatTabs::handleRequestedPreviousTab() {
- int newIndex = tabs_->currentIndex() - 1;
- tabs_->setCurrentIndex(newIndex >= 0 ? newIndex : tabs_->count() - 1);
+ int newIndex = dynamicGrid_->currentIndex() - 1;
+ dynamicGrid_->setCurrentIndex(newIndex >= 0 ? newIndex : dynamicGrid_->count() - 1);
}
void QtChatTabs::handleRequestedNextTab() {
- int newIndex = tabs_->currentIndex() + 1;
- tabs_->setCurrentIndex(newIndex < tabs_->count() ? newIndex : 0);
+ int newIndex = dynamicGrid_->currentIndex() + 1;
+ dynamicGrid_->setCurrentIndex(newIndex < dynamicGrid_->count() ? newIndex : 0);
}
void QtChatTabs::handleRequestedActiveTab() {
@@ -143,16 +212,16 @@ void QtChatTabs::handleRequestedActiveTab() {
bool finished = false;
for (int j = 0; j < 2; j++) {
bool looped = false;
- for (int i = tabs_->currentIndex() + 1; !finished && i != tabs_->currentIndex(); i++) {
- if (i >= tabs_->count()) {
+ for (int i = dynamicGrid_->currentIndex() + 1; !finished && i != dynamicGrid_->currentIndex(); i++) {
+ if (i >= dynamicGrid_->count()) {
if (looped) {
break;
}
looped = true;
i = 0;
}
- if (qobject_cast<QtTabbable*>(tabs_->widget(i))->getWidgetAlertState() == types[j]) {
- tabs_->setCurrentIndex(i);
+ if (qobject_cast<QtTabbable*>(dynamicGrid_->widget(i))->getWidgetAlertState() == types[j]) {
+ dynamicGrid_->setCurrentIndex(i);
finished = true;
break;
}
@@ -162,7 +231,13 @@ void QtChatTabs::handleRequestedActiveTab() {
void QtChatTabs::handleTabCloseRequested(int index) {
- QWidget* widget = tabs_->widget(index);
+ if (trellisMode_) {
+ storeTabPositions();
+ }
+
+ assert(index < dynamicGrid_->count());
+ QWidget* widget = dynamicGrid_->widget(index);
+ assert(widget);
widget->close();
}
@@ -176,7 +251,7 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
return;
}
QtTabbable* tabbable = qobject_cast<QtTabbable*>(widget);
- int index = tabs_->indexOf(widget);
+ int index = dynamicGrid_->indexOf(widget);
if (index < 0) {
return;
}
@@ -202,11 +277,14 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
accelsTaken[i] = (i == 0); //A is used for 'switch to active tab'
i++;
}
- int other = tabs_->tabBar()->count();
+
+ int other = dynamicGrid_->count();
while (other >= 0) {
other--;
if (other != index) {
- QString t = tabs_->tabBar()->tabText(other).toLower();
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = dynamicGrid_->indexToTabWidget(other, tabIndex);
+ QString t = tabWidget->tabBar()->tabText(tabIndex).toLower();
int r = t.indexOf('&');
if (r >= 0 && t[r+1] >= 'a' && t[r+1] <= 'z') {
accelsTaken[t[r+1].unicode()-'a'] = true;
@@ -235,24 +313,26 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
// doesn't work on Arabic/Indic keyboards (where Latin letters
// aren't available), but I don't care to deal with those.
- tabs_->setTabText(index, tabbable->getCount() > 0 ? QString("(%1) %2").arg(tabbable->getCount()).arg(tabText) : tabText);
+ int tabIndex = -1;
+ QtTabWidget* tabWidget = dynamicGrid_->indexToTabWidget(index, tabIndex);
+ tabWidget->setTabText(tabIndex, tabbable->getCount() > 0 ? QString("(%1) %2").arg(tabbable->getCount()).arg(tabText) : tabText);
QColor tabTextColor;
switch (tabbable->getWidgetAlertState()) {
case QtTabbable::WaitingActivity : tabTextColor = QColor(217, 20, 43); break;
case QtTabbable::ImpendingActivity : tabTextColor = QColor(27, 171, 32); break;
case QtTabbable::NoActivity : tabTextColor = QColor(); break;
}
- tabs_->tabBar()->setTabTextColor(index, tabTextColor);
+ tabWidget->tabBar()->setTabTextColor(tabIndex, tabTextColor);
std::vector<std::pair<std::string, int> > unreads;
- for (int i = 0; i < tabs_->count(); i++) {
- QtTabbable* tab = qobject_cast<QtTabbable*>(tabs_->widget(i));
+ for (int i = 0; i < dynamicGrid_->count(); i++) {
+ QtTabbable* tab = qobject_cast<QtTabbable*>(dynamicGrid_->widget(i));
if (tab) {
unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount()));
}
}
- std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(tabs_->currentWidget())->windowTitle()));
+ std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(dynamicGrid_->currentWidget())->windowTitle()));
ChatMessageSummarizer summary;
QString title = summary.getSummary(current, unreads).c_str();
setWindowTitle(title);
@@ -265,6 +345,29 @@ void QtChatTabs::flash() {
#endif
}
+void QtChatTabs::handleOpenLayoutChangeDialog() {
+ disconnect(gridSelectionDialog_, SIGNAL(currentGridSizeChanged(QSize)), dynamicGrid_, SLOT(setDimensions(QSize)));
+ gridSelectionDialog_->setCurrentGridSize(dynamicGrid_->getDimension());
+ gridSelectionDialog_->move(QCursor::pos());
+ connect(gridSelectionDialog_, SIGNAL(currentGridSizeChanged(QSize)), dynamicGrid_, SLOT(setDimensions(QSize)));
+ gridSelectionDialog_->show();
+}
+
+void QtChatTabs::storeTabPositions() {
+ // save size
+ QByteArray gridSizeData;
+ QDataStream dataStreamGridSize(&gridSizeData, QIODevice::ReadWrite);
+ dataStreamGridSize << dynamicGrid_->getDimension();
+ settingsProvider_->storeSetting(SettingConstants::TRELLIS_GRID_SIZE, Q2PSTRING(QString(gridSizeData.toBase64())));
+
+ // save positions
+ QByteArray tabPositionsData;
+ QDataStream dataStreamTabPositions(&tabPositionsData, QIODevice::ReadWrite);
+ dynamicGrid_->updateTabPositions();
+ dataStreamTabPositions << dynamicGrid_->getTabPositions();
+ settingsProvider_->storeSetting(SettingConstants::TRELLIS_GRID_POSITIONS, Q2PSTRING(QString(tabPositionsData.toBase64())));
+}
+
void QtChatTabs::resizeEvent(QResizeEvent*) {
emit geometryChanged();
}
diff --git a/Swift/QtUI/QtChatTabs.h b/Swift/QtUI/QtChatTabs.h
index f9cd685..99ab74a 100644
--- a/Swift/QtUI/QtChatTabs.h
+++ b/Swift/QtUI/QtChatTabs.h
@@ -1,26 +1,36 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
-#include "QtTabbable.h"
-#include "QtTabWidget.h"
#include <QWidget>
#include <QRect>
class QTabWidget;
+class QMenu;
namespace Swift {
+ class SettingsProvider;
+
+ class QtTabbable;
+ class QtTabWidget;
+ class QtDynamicGridLayout;
+ class QtGridSelectionDialog;
+
class QtChatTabs : public QWidget {
Q_OBJECT
public:
- QtChatTabs(bool singleWindow);
+ QtChatTabs(bool singleWindow, SettingsProvider* settingsProvider, bool trellisMode);
+ virtual ~QtChatTabs();
+
void addTab(QtTabbable* tab);
void minimise();
QtTabbable* getCurrentTab();
+ void setViewMenu(QMenu* viewMenu);
+
signals:
void geometryChanged();
void onTitleChanged(const QString& title);
@@ -42,10 +52,18 @@ namespace Swift {
void handleRequestedActiveTab();
void flash();
+ void handleOpenLayoutChangeDialog();
+
private:
+ void storeTabPositions();
void checkForFirstShow();
- QtTabWidget* tabs_;
+
+ private:
bool singleWindow_;
+ SettingsProvider* settingsProvider_;
+ bool trellisMode_;
+ QtDynamicGridLayout* dynamicGrid_;
+ QtGridSelectionDialog* gridSelectionDialog_;
};
}
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index a54bf8d..964f67d 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -57,7 +57,7 @@
namespace Swift {
-QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), nextAlertId_(0), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) {
+QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), id_(Q2PSTRING(contact)), contact_(contact), nextAlertId_(0), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) {
settings_ = settings;
unreadCount_ = 0;
isOnline_ = true;
@@ -170,7 +170,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1));
messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS));
-
+ setMinimumSize(100, 100);
}
QtChatWindow::~QtChatWindow() {
@@ -775,6 +775,10 @@ void QtChatWindow::showBookmarkWindow(const MUCBookmark& bookmark) {
window->show();
}
+std::string QtChatWindow::getID() const {
+ return id_;
+}
+
void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
if (mucConfigurationWindow_) {
delete mucConfigurationWindow_.data();
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index a73782a..b3d8c3e 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -78,7 +78,7 @@ namespace Swift {
public:
QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings);
- ~QtChatWindow();
+ virtual ~QtChatWindow();
std::string addMessage(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
std::string addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
@@ -130,6 +130,7 @@ namespace Swift {
void setBlockingState(BlockingState state);
virtual void setCanInitiateImpromptuChats(bool supportsImpromptu);
virtual void showBookmarkWindow(const MUCBookmark& bookmark);
+ virtual std::string getID() const;
public slots:
void handleChangeSplitterState(QByteArray state);
@@ -178,10 +179,11 @@ namespace Swift {
void handleOccupantSelectionChanged(RosterItem* item);
void handleAppendedToLog();
-
+ private:
int unreadCount_;
bool contactIsTyping_;
LastLineTracker lastLineTracker_;
+ std::string id_;
QString contact_;
QString lastSentMessage_;
QTextCursor tabCompleteCursor_;
diff --git a/Swift/QtUI/QtFileTransferListWidget.cpp b/Swift/QtUI/QtFileTransferListWidget.cpp
index 01c632f..fb2b4de 100644
--- a/Swift/QtUI/QtFileTransferListWidget.cpp
+++ b/Swift/QtUI/QtFileTransferListWidget.cpp
@@ -69,6 +69,10 @@ void QtFileTransferListWidget::setFileTransferOverview(FileTransferOverview *ove
itemModel->setFileTransferOverview(overview);
}
+std::string QtFileTransferListWidget::getID() const {
+ return "QtFileTransferListWidget";
+}
+
void QtFileTransferListWidget::closeEvent(QCloseEvent* event) {
emit windowClosing();
event->accept();
diff --git a/Swift/QtUI/QtFileTransferListWidget.h b/Swift/QtUI/QtFileTransferListWidget.h
index c828d4e..8adc009 100644
--- a/Swift/QtUI/QtFileTransferListWidget.h
+++ b/Swift/QtUI/QtFileTransferListWidget.h
@@ -31,6 +31,8 @@ public:
void setFileTransferOverview(FileTransferOverview *);
+ virtual std::string getID() const;
+
private:
virtual void closeEvent(QCloseEvent* event);
virtual void showEvent(QShowEvent* event);
diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp
index 75eeaad..ae03d7e 100644
--- a/Swift/QtUI/QtHistoryWindow.cpp
+++ b/Swift/QtUI/QtHistoryWindow.cpp
@@ -261,4 +261,8 @@ boost::gregorian::date QtHistoryWindow::getLastVisibleDate() {
return boost::gregorian::date(boost::gregorian::not_a_date_time);
}
+std::string QtHistoryWindow::getID() const {
+ return "QtHistoryWindow";
+}
+
}
diff --git a/Swift/QtUI/QtHistoryWindow.h b/Swift/QtUI/QtHistoryWindow.h
index fcbfd7e..04ea61d 100644
--- a/Swift/QtUI/QtHistoryWindow.h
+++ b/Swift/QtUI/QtHistoryWindow.h
@@ -49,6 +49,8 @@ namespace Swift {
std::string getSearchBoxText();
boost::gregorian::date getLastVisibleDate();
+ virtual std::string getID() const;
+
signals:
void fontResized(int);
diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp
index eeb4317..21095f6 100644
--- a/Swift/QtUI/QtLoginWindow.cpp
+++ b/Swift/QtUI/QtLoginWindow.cpp
@@ -490,7 +490,9 @@ void QtLoginWindow::morphInto(MainWindow *mainWindow) {
stack_->setCurrentWidget(qtMainWindow);
setEnabled(true);
setInitialMenus();
- foreach (QMenu* menu, qtMainWindow->getMenus()) {
+ std::vector<QMenu*> mainWindowMenus = qtMainWindow->getMenus();
+ viewMenu_ = mainWindowMenus[0];
+ foreach (QMenu* menu, mainWindowMenus) {
menuBar_->addMenu(menu);
}
setFocus();
diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h
index 7415fbf..55412bf 100644
--- a/Swift/QtUI/QtLoginWindow.h
+++ b/Swift/QtUI/QtLoginWindow.h
@@ -97,6 +97,7 @@ namespace Swift {
QMenuBar* menuBar_;
QMenu* swiftMenu_;
QMenu* generalMenu_;
+ QMenu* viewMenu_;
QAction* toggleSoundsAction_;
QAction* toggleNotificationsAction_;
UIEventStream* uiEventStream_;
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 52b6bcc..55c2727 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -6,41 +6,41 @@
#include <Swift/QtUI/QtMainWindow.h>
-#include <boost/optional.hpp>
#include <boost/bind.hpp>
+#include <boost/optional.hpp>
#include <boost/smart_ptr/make_shared.hpp>
+#include <QAction>
#include <QBoxLayout>
#include <QComboBox>
#include <QLineEdit>
#include <QListWidget>
#include <QListWidgetItem>
-#include <QPushButton>
#include <QMenuBar>
-#include <QToolBar>
-#include <QAction>
+#include <QPushButton>
#include <QTabWidget>
+#include <QToolBar>
#include <Swiften/Base/Platform.h>
-#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
-#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h>
+#include <Swift/Controllers/SettingConstants.h>
#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
-#include <Swift/Controllers/SettingConstants.h>
+#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h>
-#include <Swift/QtUI/Roster/QtFilterWidget.h>
+#include <Swift/QtUI/QtAdHocCommandWithJIDWindow.h>
+#include <Swift/QtUI/QtLoginWindow.h>
+#include <Swift/QtUI/QtSettingsProvider.h>
#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtTabWidget.h>
-#include <Swift/QtUI/QtSettingsProvider.h>
-#include <Swift/QtUI/QtLoginWindow.h>
-#include <Swift/QtUI/Roster/QtRosterWidget.h>
#include <Swift/QtUI/QtUISettingConstants.h>
-#include <Swift/QtUI/QtAdHocCommandWithJIDWindow.h>
+#include <Swift/QtUI/Roster/QtFilterWidget.h>
+#include <Swift/QtUI/Roster/QtRosterWidget.h>
#if defined(SWIFTEN_PLATFORM_MACOSX)
#include <Swift/QtUI/CocoaUIHelpers.h>
#elif defined(SWIFTEN_PLATFORM_WINDOWS)
@@ -139,12 +139,6 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr
handleShowEmoticonsToggled(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS));
}
- //QAction* compactRosterAction_ = new QAction(tr("&Compact Roster"), this);
- //compactRosterAction_->setCheckable(true);
- //compactRosterAction_->setChecked(false);
- //connect(compactRosterAction_, SIGNAL(toggled(bool)), uiPreferences_, SLOT(setCompactRosters(bool)));
- //viewMenu->addAction(compactRosterAction_);
-
QMenu* actionsMenu = new QMenu(tr("&Actions"), this);
menus_.push_back(actionsMenu);
QAction* editProfileAction = new QAction(tr("Edit &Profile…"), this);
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index d2224ba..5e798cf 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2013 Kevin Smith
+ * Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -90,6 +90,7 @@ po::options_description QtSwift::getOptionsDescription() {
("multi-account", po::value<int>()->default_value(1), "Number of accounts to open windows for (unsupported)")
("start-minimized", "Don't show the login/roster window at startup")
("enable-jid-adhocs", "Enable AdHoc commands to custom JID's.")
+ ("trellis", "Enable support for trellis layout")
#if QT_VERSION >= 0x040800
("language", po::value<std::string>(), "Use a specific language, instead of the system-wide one")
#endif
@@ -164,7 +165,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
}
bool enableAdHocCommandOnJID = options.count("enable-jid-adhocs") > 0;
- tabs_ = options.count("no-tabs") && !splitter_ ? NULL : new QtChatTabs(splitter_ != NULL);
+ tabs_ = options.count("no-tabs") && !splitter_ ? NULL : new QtChatTabs(splitter_ != NULL, settingsHierachy_, options.count("trellis"));
bool startMinimized = options.count("start-minimized") > 0;
applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME);
storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir(), networkFactories_.getCryptoProvider());
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index 1ea8886..fb8224c 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
diff --git a/Swift/QtUI/QtTabWidget.cpp b/Swift/QtUI/QtTabWidget.cpp
index 614439a..a5e6cdc 100644
--- a/Swift/QtUI/QtTabWidget.cpp
+++ b/Swift/QtUI/QtTabWidget.cpp
@@ -20,4 +20,7 @@ QTabBar* QtTabWidget::tabBar() {
return QTabWidget::tabBar();
}
+void QtTabWidget::setTabBar(QTabBar* tabBar) {
+ QTabWidget::setTabBar(tabBar);
+}
}
diff --git a/Swift/QtUI/QtTabWidget.h b/Swift/QtUI/QtTabWidget.h
index e4a44ce..7a73b75 100644
--- a/Swift/QtUI/QtTabWidget.h
+++ b/Swift/QtUI/QtTabWidget.h
@@ -5,14 +5,17 @@
*/
#pragma once
+
#include <QTabWidget>
namespace Swift {
class QtTabWidget : public QTabWidget {
- Q_OBJECT
+ Q_OBJECT
public:
QtTabWidget(QWidget* parent);
- ~QtTabWidget();
+ virtual ~QtTabWidget();
+
QTabBar* tabBar();
+ void setTabBar(QTabBar* tabBar);
};
}
diff --git a/Swift/QtUI/QtTabbable.cpp b/Swift/QtUI/QtTabbable.cpp
index 124d1b4..cc901c7 100644
--- a/Swift/QtUI/QtTabbable.cpp
+++ b/Swift/QtUI/QtTabbable.cpp
@@ -8,6 +8,7 @@
#include <QApplication>
#include <QKeyEvent>
+#include <QShortcut>
#include <Swiften/Base/foreach.h>
#include <Swiften/Base/Platform.h>
@@ -45,13 +46,17 @@ bool QtTabbable::event(QEvent* event) {
if (keyEvent) {
// According to Qt's focus documentation, one can only override CTRL+TAB via reimplementing QWidget::event().
#ifdef SWIFTEN_PLATFORM_LINUX
- if (keyEvent->modifiers() == QtUtilities::ctrlHardwareKeyModifier && keyEvent->key() == Qt::Key_Tab && event->type() != QEvent::KeyRelease) {
+ if (keyEvent->modifiers().testFlag(QtUtilities::ctrlHardwareKeyModifier) && keyEvent->key() == Qt::Key_Tab && event->type() != QEvent::KeyRelease) {
#else
- if (keyEvent->modifiers() == QtUtilities::ctrlHardwareKeyModifier && keyEvent->key() == Qt::Key_Tab) {
+ if (keyEvent->modifiers().testFlag(QtUtilities::ctrlHardwareKeyModifier) && keyEvent->key() == Qt::Key_Tab) {
#endif
emit requestNextTab();
return true;
}
+ else if (keyEvent->modifiers().testFlag(QtUtilities::ctrlHardwareKeyModifier) && keyEvent->key() == Qt::Key_Backtab) {
+ emit requestPreviousTab();
+ return true;
+ }
}
return QWidget::event(event);
}
diff --git a/Swift/QtUI/QtTabbable.h b/Swift/QtUI/QtTabbable.h
index fc131ca..7201efe 100644
--- a/Swift/QtUI/QtTabbable.h
+++ b/Swift/QtUI/QtTabbable.h
@@ -1,15 +1,17 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2014 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
-#include <QWidget>
-#include <QShortcut>
+#include <string>
+
#include <QList>
+#include <QWidget>
+class QShortcut;
namespace Swift {
class QtTabbable : public QWidget {
@@ -20,6 +22,8 @@ namespace Swift {
bool isWidgetSelected();
virtual AlertType getWidgetAlertState() {return NoActivity;}
virtual int getCount() {return 0;}
+ virtual std::string getID() const = 0;
+
protected:
QtTabbable();
bool event(QEvent* event);
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index b0c1492..e99b305 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -82,6 +82,7 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) {
lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_, enableAdHocCommandOnJID_);
+ tabs->setViewMenu(lastMainWindow->getMenus()[0]);
return lastMainWindow;
}
diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp
index f8ab28e..f125fc0 100644
--- a/Swift/QtUI/QtXMLConsoleWidget.cpp
+++ b/Swift/QtUI/QtXMLConsoleWidget.cpp
@@ -89,6 +89,10 @@ void QtXMLConsoleWidget::handleDataWritten(const SafeByteArray& data) {
appendTextIfEnabled(tag + "\n" + safeByteArrayToString(data) + "\n", QColor(155,1,0));
}
+std::string QtXMLConsoleWidget::getID() const {
+ return "QtXMLConsoleWidget";
+}
+
void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QColor& color) {
if (enabled->isChecked()) {
QScrollBar* scrollBar = textEdit->verticalScrollBar();
diff --git a/Swift/QtUI/QtXMLConsoleWidget.h b/Swift/QtUI/QtXMLConsoleWidget.h
index 912ad90..3cf27cf 100644
--- a/Swift/QtUI/QtXMLConsoleWidget.h
+++ b/Swift/QtUI/QtXMLConsoleWidget.h
@@ -27,6 +27,8 @@ namespace Swift {
virtual void handleDataRead(const SafeByteArray& data);
virtual void handleDataWritten(const SafeByteArray& data);
+ virtual std::string getID() const;
+
private:
virtual void closeEvent(QCloseEvent* event);
virtual void showEvent(QShowEvent* event);
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 80daaea..6e90cd4 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -193,7 +193,10 @@ sources = [
"QtUISettingConstants.cpp",
"QtURLValidator.cpp",
"QtResourceHelper.cpp",
- "QtSpellCheckHighlighter.cpp"
+ "QtSpellCheckHighlighter.cpp",
+ "Trellis/QtDynamicGridLayout.cpp",
+ "Trellis/QtDNDTabBar.cpp",
+ "Trellis/QtGridSelectionDialog.cpp"
]
# QtVCardWidget
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;
+ };
+}