From 437cadf360fbe03e602d34fd69cf3852db3dd4c3 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Fri, 7 Mar 2014 16:41:13 +0200 Subject: [PATCH] Added a basic version of the chat window. --- interface/src/Menu.cpp | 20 ++- interface/src/Menu.h | 6 + interface/src/ui/ChatWindow.cpp | 129 ++++++++++++++++ interface/src/ui/ChatWindow.h | 40 +++++ interface/src/ui/FlowLayout.cpp | 213 ++++++++++++++++++++++++++ interface/src/ui/FlowLayout.h | 78 ++++++++++ interface/ui/chatWindow.ui | 125 +++++++++++++++ libraries/shared/src/AccountManager.h | 2 + 8 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 interface/src/ui/ChatWindow.cpp create mode 100644 interface/src/ui/ChatWindow.h create mode 100644 interface/src/ui/FlowLayout.cpp create mode 100644 interface/src/ui/FlowLayout.h create mode 100644 interface/ui/chatWindow.ui diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e4fa0c49e0..60e93b0cea 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -161,7 +161,7 @@ Menu::Menu() : QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::FstUploader, 0, Application::getInstance(), SLOT(uploadFST())); - + addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, 0, this, SLOT(showChat())); QMenu* viewMenu = addMenu("View"); @@ -1021,6 +1021,24 @@ void Menu::showMetavoxelEditor() { _MetavoxelEditor->raise(); } +void Menu::showChat() { + if (!_chatWindow) { + _chatWindow = new ChatWindow(); + QMainWindow* mainWindow = Application::getInstance()->getWindow(); + + // the height of the title bar is given by frameGeometry().height() - geometry().height() + // however, frameGeometry() is initialised after showing (Qt queries the OS windowing system) + // on the other hand, moving a window after showing it flickers; so just use some reasonable value + int titleBarHeight = 16; + _chatWindow->setGeometry(mainWindow->width() - _chatWindow->width(), + mainWindow->geometry().y() + titleBarHeight, + _chatWindow->width(), + mainWindow->height() - titleBarHeight); + _chatWindow->show(); + } + _chatWindow->raise(); +} + void Menu::audioMuteToggled() { QAction *muteAction = _actionHash.value(MenuOption::MuteAudio); muteAction->setChecked(Application::getInstance()->getAudio()->getMuted()); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index d8a7672972..cd32a9c8df 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -18,6 +18,8 @@ #include #include +#include + const float ADJUST_LOD_DOWN_FPS = 40.0; const float ADJUST_LOD_UP_FPS = 55.0; @@ -53,6 +55,7 @@ class QSettings; class BandwidthDialog; class LodToolsDialog; class MetavoxelEditor; +class ChatWindow; class OctreeStatsDialog; class MenuItemProperties; @@ -140,6 +143,7 @@ private slots: void cycleFrustumRenderMode(); void runTests(); void showMetavoxelEditor(); + void showChat(); void audioMuteToggled(); private: @@ -187,6 +191,7 @@ private: FrustumDrawMode _frustumDrawMode; ViewFrustumOffset _viewFrustumOffset; QPointer _MetavoxelEditor; + QPointer _chatWindow; OctreeStatsDialog* _octreeStatsDialog; LodToolsDialog* _lodToolsDialog; int _maxVoxels; @@ -252,6 +257,7 @@ namespace MenuOption { const QString Logout = "Logout"; const QString LookAtVectors = "Look-at Vectors"; const QString MetavoxelEditor = "Metavoxel Editor..."; + const QString Chat = "Chat..."; const QString Metavoxels = "Metavoxels"; const QString Mirror = "Mirror"; const QString MoveWithLean = "Move with Lean"; diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp new file mode 100644 index 0000000000..5770eff31e --- /dev/null +++ b/interface/src/ui/ChatWindow.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include + +#include "ChatWindow.h" +#include "ui_chatwindow.h" +#include "FlowLayout.h" + +#include +#include +#include +#include + +const QString DEFAULT_SERVER = "chat.highfidelity.io"; +const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; + +const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); + +ChatWindow::ChatWindow() : + QDialog(Application::getInstance()->getGLWidget(), Qt::Tool), + ui(new Ui::ChatWindow) { + ui->setupUi(this); + + FlowLayout* flowLayout = new FlowLayout(); + flowLayout->setContentsMargins(0, 8, 0, 8); + ui->usersWidget->setLayout(flowLayout); + + ui->messagePlainTextEdit->installEventFilter(this); + + ui->numOnlineLabel->hide(); + ui->usersWidget->hide(); + ui->messagesScrollArea->hide(); + ui->messagePlainTextEdit->hide(); + + setAttribute(Qt::WA_DeleteOnClose); + + _xmppClient.addExtension(&_xmppMUCManager); + connect(&_xmppClient, SIGNAL(connected()), this, SLOT(connected())); + connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(error(QXmppClient::Error))); + connect(&_xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); + + AccountManager& accountManager = AccountManager::getInstance(); + QString user = accountManager.getUsername(); + const QString& password = accountManager.getXMPPPassword(); + _xmppClient.connectToServer(user + "@" + DEFAULT_SERVER, password); +} + +ChatWindow::~ChatWindow() { + delete ui; +} + +bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { + Q_UNUSED(sender); + + if (event->type() != QEvent::KeyPress) { + return false; + } + QKeyEvent* keyEvent = static_cast(event); + if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && + (keyEvent->modifiers() & Qt::ShiftModifier) == 0) { + _xmppClient.sendMessage(_chatRoom->jid(), ui->messagePlainTextEdit->document()->toPlainText()); + ui->messagePlainTextEdit->document()->clear(); + return true; + } + return false; +} + +QString ChatWindow::getParticipantName(const QString& participant) { + return participant.right(participant.count() - 1 - _chatRoom->jid().count()); +} + +void ChatWindow::connected() { + _chatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); + connect(_chatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + _chatRoom->setNickName(AccountManager::getInstance().getUsername()); + _chatRoom->join(); + + ui->connectingToXMPPLabel->hide(); + ui->numOnlineLabel->show(); + ui->usersWidget->show(); + ui->messagesScrollArea->show(); + ui->messagePlainTextEdit->show(); +} + +void ChatWindow::error(QXmppClient::Error error) { + ui->connectingToXMPPLabel->setText(QString::number(error)); +} + +void ChatWindow::participantsChanged() { + QStringList participants = _chatRoom->participants(); + ui->numOnlineLabel->setText(tr("%1 online now:").arg(participants.count())); + + while (QLayoutItem* item = ui->usersWidget->layout()->takeAt(0)) { + delete item; + } + foreach (const QString& participant, participants) { + QLabel* userLabel = new QLabel(getParticipantName(participant)); + userLabel->setStyleSheet( + "background-color: palette(light);" + "border-radius: 5px;" + "color: #267077;" + "padding: 2px;" + "border: 1px solid palette(shadow);" + "font-weight: bold"); + ui->usersWidget->layout()->addWidget(userLabel); + } +} + +void ChatWindow::messageReceived(const QXmppMessage& message) { + QLabel* userLabel = new QLabel(getParticipantName(message.from())); + QFont font = userLabel->font(); + font.setBold(true); + userLabel->setFont(font); + userLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + QLabel* messageLabel = new QLabel(message.body().replace(regexLinks, "\\1")); + messageLabel->setWordWrap(true); + messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + messageLabel->setOpenExternalLinks(true); + + ui->messagesFormLayout->addRow(userLabel, messageLabel); + ui->messagesFormLayout->parentWidget()->updateGeometry(); + Application::processEvents(); + QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); + verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); +} diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h new file mode 100644 index 0000000000..c1963b5342 --- /dev/null +++ b/interface/src/ui/ChatWindow.h @@ -0,0 +1,40 @@ +#ifndef __interface__ChatWindow__ +#define __interface__ChatWindow__ + +#include + +#include + +#include +#include + +namespace Ui { +class ChatWindow; +} + +class ChatWindow : public QDialog { + Q_OBJECT + +public: + ChatWindow(); + ~ChatWindow(); + +protected: + bool eventFilter(QObject* sender, QEvent* event); + +private: + Ui::ChatWindow* ui; + QXmppClient _xmppClient; + QXmppMucManager _xmppMUCManager; + QXmppMucRoom* _chatRoom; + + QString getParticipantName(const QString& participant); + +private slots: + void connected(); + void error(QXmppClient::Error error); + void participantsChanged(); + void messageReceived(const QXmppMessage& message); +}; + +#endif /* defined(__interface__ChatWindow__) */ diff --git a/interface/src/ui/FlowLayout.cpp b/interface/src/ui/FlowLayout.cpp new file mode 100644 index 0000000000..5387d9499d --- /dev/null +++ b/interface/src/ui/FlowLayout.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "flowlayout.h" +//! [1] +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} +//! [1] + +//! [2] +FlowLayout::~FlowLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} +//! [2] + +//! [3] +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} +//! [3] + +//! [4] +int FlowLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FlowLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} +//! [4] + +//! [5] +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem *FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem *FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return 0; +} +//! [5] + +//! [6] +Qt::Orientations FlowLayout::expandingDirections() const +{ + return 0; +} +//! [6] + +//! [7] +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} +//! [7] + +//! [8] +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + QLayoutItem *item; + foreach (item, itemList) + size = size.expandedTo(item->minimumSize()); + + size += QSize(2*margin(), 2*margin()); + return size; +} +//! [8] + +//! [9] +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; +//! [9] + +//! [10] + QLayoutItem *item; + foreach (item, itemList) { + QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); +//! [10] +//! [11] + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} +//! [11] +//! [12] +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, 0, pw); + } else { + return static_cast(parent)->spacing(); + } +} +//! [12] diff --git a/interface/src/ui/FlowLayout.h b/interface/src/ui/FlowLayout.h new file mode 100644 index 0000000000..f7107be6b2 --- /dev/null +++ b/interface/src/ui/FlowLayout.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include +#include +#include +//! [0] +class FlowLayout : public QLayout +{ +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout(); + + void addItem(QLayoutItem *item); + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const; + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int count() const; + QLayoutItem *itemAt(int index) const; + QSize minimumSize() const; + void setGeometry(const QRect &rect); + QSize sizeHint() const; + QLayoutItem *takeAt(int index); + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +}; +//! [0] + +#endif // FLOWLAYOUT_H diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui new file mode 100644 index 0000000000..554b42833e --- /dev/null +++ b/interface/ui/chatWindow.ui @@ -0,0 +1,125 @@ + + + ChatWindow + + + + 0 + 0 + 400 + 608 + + + + Chat + + + + 0 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + 0 + 0 + + + + Connecting to XMPP... + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + font-weight: bold; color: palette(shadow) + + + online now: + + + + + + + + + + true + + + + + 0 + 0 + 382 + 356 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + + + + 0 + 0 + + + + + + + + messagePlainTextEdit + messagesScrollArea + + + + diff --git a/libraries/shared/src/AccountManager.h b/libraries/shared/src/AccountManager.h index 8b3c6b362c..ee821fa43c 100644 --- a/libraries/shared/src/AccountManager.h +++ b/libraries/shared/src/AccountManager.h @@ -52,6 +52,8 @@ public: void requestAccessToken(const QString& login, const QString& password); QString getUsername() const { return _accountInfo.getUsername(); } + + const QString& getXMPPPassword() const { return _accountInfo.getXMPPPassword(); } void destroy() { delete _networkAccessManager; }