From 6a2e6d58233cbf40b34f962f2b2f9b1589969e13 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Tue, 17 Mar 2015 16:00:02 +0100
Subject: Fix keyboard shortcuts for --no-tabs mode

This commit enables the following shortcuts for --no-tabs mode:
- CTRL + Tab or CTRL/CMD + PageDown to switch to the next chat window
- CTRL + Shift + Tab or CTRL/CMD + PageUp to switch to the previous chat window
- CTRL/CMD + W to close the current chat window
- ALT + A to switch to the next chat window with active messages

Test-Information:

Verified that the new shortcuts work as expected and verified that
standard mode and netbook mode still work as usual.

Change-Id: I3831b6c02f5d664cc8b21d7571e20aed00de89b4

diff --git a/Swift/QtUI/QtChatTabs.h b/Swift/QtUI/QtChatTabs.h
index bee03ec..71d4ddc 100644
--- a/Swift/QtUI/QtChatTabs.h
+++ b/Swift/QtUI/QtChatTabs.h
@@ -10,6 +10,8 @@
 #include <QRect>
 #include <QShortcut>
 
+#include <Swift/QtUI/QtChatTabsBase.h>
+
 class QTabWidget;
 class QMenu;
 
@@ -21,13 +23,13 @@ namespace Swift {
 	class QtDynamicGridLayout;
 	class QtGridSelectionDialog;
 
-	class QtChatTabs : public QWidget {
+	class QtChatTabs : public QWidget, public QtChatTabsBase {
 		Q_OBJECT
 		public:
 			QtChatTabs(bool singleWindow, SettingsProvider* settingsProvider, bool trellisMode);
 			virtual ~QtChatTabs();
 
-			void addTab(QtTabbable* tab);
+			virtual void addTab(QtTabbable* tab);
 			void minimise();
 			QtTabbable* getCurrentTab();
 			void setViewMenu(QMenu* viewMenu);
diff --git a/Swift/QtUI/QtChatTabsBase.cpp b/Swift/QtUI/QtChatTabsBase.cpp
new file mode 100644
index 0000000..140ff08
--- /dev/null
+++ b/Swift/QtUI/QtChatTabsBase.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtChatTabsBase.h>
+
+namespace Swift {
+
+QtChatTabsBase::QtChatTabsBase() {
+
+}
+
+QtChatTabsBase::~QtChatTabsBase() {
+
+}
+
+}
diff --git a/Swift/QtUI/QtChatTabsBase.h b/Swift/QtUI/QtChatTabsBase.h
new file mode 100644
index 0000000..753b706
--- /dev/null
+++ b/Swift/QtUI/QtChatTabsBase.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+
+class QtTabbable;
+
+class QtChatTabsBase {
+	public:
+		QtChatTabsBase();
+		virtual ~QtChatTabsBase();
+
+		virtual void addTab(QtTabbable* tab) = 0;
+};
+
+}
diff --git a/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.cpp b/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.cpp
new file mode 100644
index 0000000..7f44177
--- /dev/null
+++ b/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h>
+
+#include <cassert>
+
+#include <QApplication>
+#include <QShortcut>
+
+#include <Swiften/Base/Log.h>
+#include <Swiften/Base/foreach.h>
+
+#include <Swift/QtUI/QtTabbable.h>
+
+namespace Swift {
+
+QtChatTabsShortcutOnlySubstitute::QtChatTabsShortcutOnlySubstitute() : QWidget() {
+
+}
+
+QtChatTabsShortcutOnlySubstitute::~QtChatTabsShortcutOnlySubstitute() {
+
+}
+
+void QtChatTabsShortcutOnlySubstitute::addTab(QtTabbable* tab) {
+	connect(tab, SIGNAL(requestNextTab()), this, SLOT(handleRequestedNextTab()), Qt::UniqueConnection);
+	connect(tab, SIGNAL(requestActiveTab()), this, SLOT(handleRequestedActiveTab()), Qt::UniqueConnection);
+	connect(tab, SIGNAL(requestPreviousTab()), this, SLOT(handleRequestedPreviousTab()), Qt::UniqueConnection);
+
+	connect(new QShortcut(QKeySequence(tr("CTRL+W", "Close chat tab.")), tab), SIGNAL(activated()), this, SLOT(handleCloseTabShortcut()));
+	connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageUp), tab), SIGNAL(activated()), tab, SIGNAL(requestPreviousTab()));
+	connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageDown), tab), SIGNAL(activated()), tab, SIGNAL(requestNextTab()));
+	connect(new QShortcut(QKeySequence(Qt::ALT + Qt::Key_A), tab), SIGNAL(activated()), tab, SIGNAL(requestActiveTab()));
+}
+
+void QtChatTabsShortcutOnlySubstitute::handleCloseTabShortcut() {
+	QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender()->parent());
+	SWIFT_LOG_ASSERT(senderTab, debug) << "No window to close." << std::endl;
+	if (senderTab) {
+		senderTab->close();
+	}
+}
+
+void QtChatTabsShortcutOnlySubstitute::handleRequestedNextTab() {
+	QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender());
+
+	QList<QtTabbable*> tabs = tabbableWindows();
+
+	int currentIndex = tabs.indexOf(senderTab);
+	assert(currentIndex >= 0);
+
+	QtTabbable* nextTab = tabs.at((currentIndex + 1) % tabs.size());
+	nextTab->activateWindow();
+}
+
+void QtChatTabsShortcutOnlySubstitute::handleRequestedActiveTab() {
+	QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender());
+
+	QtTabbable::AlertType types[] = {QtTabbable::WaitingActivity, QtTabbable::ImpendingActivity};
+
+	QList<QtTabbable*> tabs = tabbableWindows();
+
+	for (int j = 0; j < 2; j++) {
+		int startIndex = tabs.indexOf(senderTab);
+		int currentIndex = startIndex;
+
+		do {
+			currentIndex = (currentIndex + 1) % tabs.size();
+			QtTabbable* currentTab = tabs.at(currentIndex);
+			if (currentTab->getWidgetAlertState() == types[j]) {
+				currentTab->activateWindow();
+				return;
+			}
+		} while (startIndex != currentIndex);
+	}
+}
+
+void QtChatTabsShortcutOnlySubstitute::handleRequestedPreviousTab() {
+	QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender());
+
+	QList<QtTabbable*> tabs = tabbableWindows();
+
+	int currentIndex = tabs.indexOf(senderTab);
+	assert(currentIndex >= 0);
+
+	QtTabbable* previousTab = tabs.at((currentIndex + tabs.size() - 1) % tabs.size());
+	previousTab->activateWindow();
+}
+
+QList<QtTabbable*> QtChatTabsShortcutOnlySubstitute::tabbableWindows() const {
+	QList<QWidget*> windows = qApp->topLevelWidgets();
+
+	QList<QtTabbable*> tabbables;
+	foreach(QWidget* topLevelWidget, windows) {
+		QtTabbable* tabbable = dynamic_cast<QtTabbable*>(topLevelWidget);
+		if (tabbable) {
+			tabbables << tabbable;
+		}
+	}
+
+	return tabbables;
+}
+
+}
+
diff --git a/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h b/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h
new file mode 100644
index 0000000..069bb0b
--- /dev/null
+++ b/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+#include <QList>
+
+#include <Swift/QtUI/QtChatTabsBase.h>
+
+class QShortcut;
+
+namespace Swift {
+
+class QtChatTabsShortcutOnlySubstitute : public QWidget, public QtChatTabsBase {
+	Q_OBJECT
+
+	public:
+		QtChatTabsShortcutOnlySubstitute();
+		virtual ~QtChatTabsShortcutOnlySubstitute();
+
+		virtual void addTab(QtTabbable* tab);
+
+	private slots:
+		void handleCloseTabShortcut();
+		void handleRequestedNextTab();
+		void handleRequestedActiveTab();
+		void handleRequestedPreviousTab();
+
+	private:
+		QList<QtTabbable*> tabbableWindows() const;
+
+	private:
+		QList<QShortcut*> shortcuts_;
+};
+
+}
diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp
index 1774653..b9ba89d 100644
--- a/Swift/QtUI/QtChatWindowFactory.cpp
+++ b/Swift/QtUI/QtChatWindowFactory.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Isode Limited.
+ * Copyright (c) 2010-2015 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -10,6 +10,7 @@
 #include <qdebug.h>
 
 #include <Swift/QtUI/QtChatTabs.h>
+#include <Swift/QtUI/QtChatTabsBase.h>
 #include <Swift/QtUI/QtChatWindow.h>
 #include <Swift/QtUI/QtSwiftUtil.h>
 #include <Swift/QtUI/QtChatTheme.h>
@@ -21,19 +22,21 @@ namespace Swift {
 static const QString SPLITTER_STATE = "mucSplitterState";
 static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry";
 
-QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticons) : themePath_(themePath), emoticons_(emoticons) {
+QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabsBase* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticons) : themePath_(themePath), emoticons_(emoticons) {
 	qtOnlySettings_ = qtSettings;
 	settings_ = settings;
 	tabs_ = tabs;
 	theme_ = NULL;
+	QtChatTabs* fullTabs = dynamic_cast<QtChatTabs*>(tabs_);
 	if (splitter) {
-		splitter->addWidget(tabs_);
-	} else if (tabs_) {
+		assert(fullTabs && "Netbook mode and no-tabs interface is not supported!");
+		splitter->addWidget(fullTabs);
+	} else if (fullTabs) {
 		QVariant chatTabsGeometryVariant = qtOnlySettings_->getQSettings()->value(CHAT_TABS_GEOMETRY);
 		if (chatTabsGeometryVariant.isValid()) {
-			tabs_->restoreGeometry(chatTabsGeometryVariant.toByteArray());
+			fullTabs->restoreGeometry(chatTabsGeometryVariant.toByteArray());
 		}
-		connect(tabs_, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged()));
+		connect(fullTabs, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged()));
 	}
 }
 
diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h
index c38202f..6b1f0a1 100644
--- a/Swift/QtUI/QtChatWindowFactory.h
+++ b/Swift/QtUI/QtChatWindowFactory.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Isode Limited.
+ * Copyright (c) 2010-2015 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -19,7 +19,7 @@
 #include <Swift/QtUI/QtSettingsProvider.h>
 
 namespace Swift {
-	class QtChatTabs;
+	class QtChatTabsBase;
 	class QtChatTheme;
 	class UIEventStream;
 	class QtUIPreferences;
@@ -27,7 +27,7 @@ namespace Swift {
 	class QtChatWindowFactory : public QObject, public ChatWindowFactory {
 		Q_OBJECT
 		public:
-			QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticons);
+			QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabsBase* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticons);
 			~QtChatWindowFactory();
 			ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);
 		signals:
@@ -39,7 +39,7 @@ namespace Swift {
 			QString themePath_;
 			SettingsProvider* settings_;
 			QtSettingsProvider* qtOnlySettings_;
-			QtChatTabs* tabs_;
+			QtChatTabsBase* tabs_;
 			QtChatTheme* theme_;
 			std::map<std::string, std::string> emoticons_;
 	};
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index bce3ca4..20d7bc9 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -38,7 +38,9 @@
 #include <Swift/Controllers/StatusCache.h>
 
 #include <Swift/QtUI/QtLoginWindow.h>
+#include <Swift/QtUI/QtChatTabsBase.h>
 #include <Swift/QtUI/QtChatTabs.h>
+#include <Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h>
 #include <Swift/QtUI/QtSystemTray.h>
 #include <Swift/QtUI/QtSoundPlayer.h>
 #include <Swift/QtUI/QtSwiftUtil.h>
@@ -165,7 +167,13 @@ 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, settingsHierachy_, options.count("trellis"));
+	tabs_ = NULL;
+	if (options.count("no-tabs") && !splitter_) {
+		tabs_ = new QtChatTabsShortcutOnlySubstitute();
+	}
+	else {
+		tabs_ = 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 cbe9c62..a971324 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2014 Isode Limited.
+ * Copyright (c) 2010-2015 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -41,7 +41,7 @@ namespace Swift {
 	class CapsStorage;
 	class MainController;
 	class QtSystemTray;
-	class QtChatTabs;
+	class QtChatTabsBase;
 	class QtChatWindowFactory;
 	class QtSoundPlayer;
 	class QtMUCSearchWindowFactory;
@@ -77,7 +77,7 @@ namespace Swift {
 			QtSoundPlayer* soundPlayer_;
 			Dock* dock_;
 			URIHandler* uriHandler_;
-			QtChatTabs* tabs_;
+			QtChatTabsBase* tabs_;
 			ApplicationPathProvider* applicationPathProvider_;
 			StoragesFactory* storagesFactory_;
 			CertificateStorageFactory* certificateStorageFactory_;
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index c7b64b3..8dece86 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -10,6 +10,7 @@
 
 #include <Swift/QtUI/QtXMLConsoleWidget.h>
 #include <Swift/QtUI/QtChatTabs.h>
+#include <Swift/QtUI/QtChatTabsBase.h>
 #include <Swift/QtUI/QtMainWindow.h>
 #include <Swift/QtUI/QtLoginWindow.h>
 #include <Swift/QtUI/QtSystemTray.h>
@@ -36,14 +37,15 @@
 
 namespace Swift {
 
-QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) {
+QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabsBase* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID) : settings(settings), qtOnlySettings(qtOnlySettings), tabsBase(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) {
 	chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE);
 	historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE);
+	this->tabs = dynamic_cast<QtChatTabs*>(tabsBase);
 }
 
 XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() {
 	QtXMLConsoleWidget* widget = new QtXMLConsoleWidget();
-	tabs->addTab(widget);
+	tabsBase->addTab(widget);
 	showTabs();
 	widget->show();
 	return widget;
@@ -51,8 +53,7 @@ XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() {
 
 HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) {
 	QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream);
-	tabs->addTab(window);
-
+	tabsBase->addTab(window);
 	showTabs();
 	connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int)));
 
@@ -68,7 +69,7 @@ void QtUIFactory::handleHistoryWindowFontResized(int size) {
 
 FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
 	QtFileTransferListWidget* widget = new QtFileTransferListWidget();
-	tabs->addTab(widget);
+	tabsBase->addTab(widget);
 	showTabs();
 	widget->show();
 	return widget;
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index 05b4f6a..1d935bc 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -16,6 +16,7 @@ class QSplitter;
 namespace Swift {
 	class QtSettingsProvider;
 	class SettingsProviderHierachy;
+	class QtChatTabsBase;
 	class QtChatTabs;
 	class QtSystemTray;
 	class QtLoginWindow;
@@ -32,7 +33,7 @@ namespace Swift {
 	class QtUIFactory : public QObject, public UIFactory {
 			Q_OBJECT
 		public:
-			QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID);
+			QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabsBase* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID);
 
 			virtual XMLConsoleWidget* createXMLConsoleWidget();
 			virtual HistoryWindow* createHistoryWindow(UIEventStream*);
@@ -63,6 +64,7 @@ namespace Swift {
 		private:
 			SettingsProviderHierachy* settings;
 			QtSettingsProvider* qtOnlySettings;
+			QtChatTabsBase* tabsBase;
 			QtChatTabs* tabs;
 			QtSingleWindow* netbookSplitter;
 			QtSystemTray* systemTray;
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 36f7cc9..f3251d2 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -108,6 +108,8 @@ sources = [
     "QtPlainChatView.cpp",
     "QtChatTheme.cpp",
     "QtChatTabs.cpp",
+    "QtChatTabsBase.cpp",
+    "QtChatTabsShortcutOnlySubstitute.cpp",
     "QtSoundPlayer.cpp",
     "QtSystemTray.cpp",
     "QtCachedImageScaler.cpp",
-- 
cgit v0.10.2-6-g49f6