From 07d3eb886a9fc515c5ebe8d3584193b3f4bb0339 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 4 Mar 2014 21:27:06 +0200 Subject: [PATCH 01/13] Added reading and storing of the XMPP password of the current user. --- .../shared/src/DataServerAccountInfo.cpp | 25 +++++++++++++++---- libraries/shared/src/DataServerAccountInfo.h | 4 +++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/libraries/shared/src/DataServerAccountInfo.cpp b/libraries/shared/src/DataServerAccountInfo.cpp index 7225653998..2bcd2dbcd0 100644 --- a/libraries/shared/src/DataServerAccountInfo.cpp +++ b/libraries/shared/src/DataServerAccountInfo.cpp @@ -12,21 +12,26 @@ DataServerAccountInfo::DataServerAccountInfo() : _accessToken(), - _username() + _username(), + _xmppPassword() { } DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) : _accessToken(jsonObject), - _username() + _username(), + _xmppPassword() { - setUsername(jsonObject["user"].toObject()["username"].toString()); + QJsonObject userJSONObject = jsonObject["user"].toObject(); + setUsername(userJSONObject["username"].toString()); + setXMPPPassword(userJSONObject["xmpp_password"].toString()); } DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) { _accessToken = otherInfo._accessToken; _username = otherInfo._username; + _xmppPassword = otherInfo._xmppPassword; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -40,6 +45,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_accessToken, otherInfo._accessToken); swap(_username, otherInfo._username); + swap(_xmppPassword, otherInfo._xmppPassword); } void DataServerAccountInfo::setUsername(const QString& username) { @@ -50,12 +56,21 @@ void DataServerAccountInfo::setUsername(const QString& username) { } } +void DataServerAccountInfo::setXMPPPassword(const QString& xmppPassword) +{ + if (_xmppPassword != xmppPassword) { + _xmppPassword = xmppPassword; + + qDebug() << "XMPP password changed to " << xmppPassword; + } +} + QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { - out << info._accessToken << info._username; + out << info._accessToken << info._username << info._xmppPassword; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { - in >> info._accessToken >> info._username; + in >> info._accessToken >> info._username >> info._xmppPassword; return in; } \ No newline at end of file diff --git a/libraries/shared/src/DataServerAccountInfo.h b/libraries/shared/src/DataServerAccountInfo.h index da7bdbe42e..c840468319 100644 --- a/libraries/shared/src/DataServerAccountInfo.h +++ b/libraries/shared/src/DataServerAccountInfo.h @@ -25,6 +25,9 @@ public: const QString& getUsername() const { return _username; } void setUsername(const QString& username); + + const QString& getXMPPPassword() const { return _xmppPassword; } + void setXMPPPassword(const QString& xmppPassword); friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info); friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info); @@ -33,6 +36,7 @@ private: OAuthAccessToken _accessToken; QString _username; + QString _xmppPassword; }; #endif /* defined(__hifi__DataServerAccountInfo__) */ From 1c2e752f3ce87055d52289c5713aad0a06447c2f Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Wed, 5 Mar 2014 19:57:59 +0200 Subject: [PATCH 02/13] Integrated QXmpp in the build system. --- CMakeLists.txt | 6 ++++++ cmake/modules/FindQxmpp.cmake | 38 +++++++++++++++++++++++++++++++++ interface/CMakeLists.txt | 4 ++++ libraries/shared/CMakeLists.txt | 5 ++--- 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 cmake/modules/FindQxmpp.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 33589ddb57..b3e1af23e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,12 @@ if (APPLE) endif (DARWIN_VERSION GREATER 12) endif (APPLE) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") + +find_package(qxmpp REQUIRED) +add_definitions(-DQXMPP_STATIC) +include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) + # targets not supported on windows if (NOT WIN32) add_subdirectory(animation-server) diff --git a/cmake/modules/FindQxmpp.cmake b/cmake/modules/FindQxmpp.cmake new file mode 100644 index 0000000000..f50212d63f --- /dev/null +++ b/cmake/modules/FindQxmpp.cmake @@ -0,0 +1,38 @@ +# Try to find the qxmpp library +# +# You can provide a QXMPP_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# QXMPP_FOUND - system found qxmpp +# QXMPP_INCLUDE_DIRS - the qxmpp include directory +# QXMPP_LIBRARIES - Link this to use qxmpp +# +# Created on 3/10/2014 by Stephen Birarda +# Copyright (c) 2014 High Fidelity +# + +if (QXMPP_LIBRARIES AND QXMPP_INCLUDE_DIRS) + # in cache already + set(QXMPP_FOUND TRUE) +else () + + set(QXMPP_SEARCH_DIRS "${QXMPP_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/qxmpp") + + find_path(QXMPP_INCLUDE_DIR QXmppClient.h PATH_SUFFIXES include/qxmpp HINTS ${QXMPP_SEARCH_DIRS}) + + find_library(QXMPP_LIBRARY NAMES qxmpp qxmpp0 PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(QXMPP DEFAULT_MSG QXMPP_INCLUDE_DIR QXMPP_LIBRARY) + + if (QXMPP_FOUND) + if (NOT QXMPP_FIND_QUIETLY) + message(STATUS "Found qxmpp: ${QXMPP_LIBRARY}") + endif (NOT QXMPP_FIND_QUIETLY) + else () + if (QXMPP_FIND_REQUIRED) + message(FATAL_ERROR "Could not find qxmpp") + endif (SIXENSE_FIND_REQUIRED) + endif () +endif () \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f73cf0fd64..9f176d1ee8 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -65,6 +65,10 @@ endforeach(EXTERNAL_SOURCE_SUBDIR) find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) +find_package(Qxmpp REQUIRED) +add_definitions(-DQXMPP_STATIC) +include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) + # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) # have qt5 wrap them and generate the appropriate header files diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 658542637f..40473c924a 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -10,7 +10,7 @@ set(MACRO_DIR "${ROOT_DIR}/cmake/macros") set(TARGET_NAME shared) project(${TARGET_NAME}) -find_package(Qt5 COMPONENTS Network Widgets) +find_package(Qt5 COMPONENTS Network Widgets Xml) include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME}) @@ -19,7 +19,6 @@ setup_hifi_library(${TARGET_NAME}) include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") - set(EXTERNAL_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external") if (WIN32) @@ -33,4 +32,4 @@ if (UNIX AND NOT APPLE) target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") endif (UNIX AND NOT APPLE) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) \ No newline at end of file +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Xml "${QXMPP_LIBRARY}") From 437cadf360fbe03e602d34fd69cf3852db3dd4c3 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Fri, 7 Mar 2014 16:41:13 +0200 Subject: [PATCH 03/13] 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; } From 9eb217794c5e038138a2d2e9cdbf4fbe5a96f1bd Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Mon, 10 Mar 2014 12:08:33 +0200 Subject: [PATCH 04/13] Added time stamps to the chat. Enabled translations. --- interface/CMakeLists.txt | 8 +- interface/interface_en.ts | 161 ++ interface/src/main.cpp | 5 + interface/src/ui/ChatWindow.cpp | 79 +- interface/src/ui/ChatWindow.h | 18 +- interface/ui/chatWindow.ui | 20 +- libraries/shared/src/qtimespan.cpp | 2290 ++++++++++++++++++++++++++++ libraries/shared/src/qtimespan.h | 301 ++++ 8 files changed, 2863 insertions(+), 19 deletions(-) create mode 100644 interface/interface_en.ts create mode 100644 libraries/shared/src/qtimespan.cpp create mode 100644 libraries/shared/src/qtimespan.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 9f176d1ee8..6ec0b6f679 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -10,6 +10,8 @@ set(MACRO_DIR "${ROOT_DIR}/cmake/macros") set(TARGET_NAME interface) project(${TARGET_NAME}) +find_package(Qt5LinguistTools REQUIRED) + # setup for find modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") set(FACESHIFT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/faceshift") @@ -77,6 +79,10 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}") +set(QM ${TARGET_NAME}_en.qm) +set(TS ${TARGET_NAME}_en.ts) +qt5_create_translation(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) + if (APPLE) # configure CMake to use a custom Info.plist SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in ) @@ -105,7 +111,7 @@ if (APPLE) endif() # create the executable, make it a bundle on OS X -add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS}) +add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) # link in the hifi shared library include(${MACRO_DIR}/LinkHifiLibrary.cmake) diff --git a/interface/interface_en.ts b/interface/interface_en.ts new file mode 100644 index 0000000000..8198d97a8f --- /dev/null +++ b/interface/interface_en.ts @@ -0,0 +1,161 @@ + + + + + Application + + + Export Voxels + + + + + Sparse Voxel Octree Files (*.svo) + + + + + Open Script + + + + + JavaScript Files (*.js) + + + + + ChatWindow + + + + Chat + + + + + + Connecting to XMPP... + + + + + + online now: + + + + + day + + %n day + %n days + + + + + hour + + %n hour + %n hours + + + + + minute + + %n minute + %n minutes + + + + second + + %n second + %n seconds + + + + + %1 online now: + + + + + Dialog + + + + + + Update Required + + + + + + Download + + + + + + Skip Version + + + + + + Close + + + + + Menu + + + Open .ini config file + + + + + + Text files (*.ini) + + + + + Save .ini config file + + + + + QObject + + + + Import Voxels + + + + + Loading ... + + + + + Place voxels + + + + + <b>Import</b> %1 as voxels + + + + + Cancel + + + + diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 2d30fc06bc..f2c516792b 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include #include int main(int argc, const char * argv[]) { @@ -40,6 +41,10 @@ int main(int argc, const char * argv[]) { { QSettings::setDefaultFormat(QSettings::IniFormat); Application app(argc, const_cast(argv), startup_time); + + QTranslator translator; + translator.load("interface_en"); + app.installTranslator(&translator); qDebug( "Created QT Application."); exitCode = app.exec(); diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 5770eff31e..4e4b869cda 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -1,13 +1,24 @@ +// +// ChatWindow.cpp +// interface +// +// Created by Dimitar Dobrev on 3/6/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + #include #include #include +#include #include #include #include +#include #include "ChatWindow.h" #include "ui_chatwindow.h" #include "FlowLayout.h" +#include "qtimespan.h" #include #include @@ -16,16 +27,17 @@ const QString DEFAULT_SERVER = "chat.highfidelity.io"; const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; +const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); ChatWindow::ChatWindow() : QDialog(Application::getInstance()->getGLWidget(), Qt::Tool), - ui(new Ui::ChatWindow) { + ui(new Ui::ChatWindow), + numMessagesAfterLastTimeStamp(0) { ui->setupUi(this); - FlowLayout* flowLayout = new FlowLayout(); - flowLayout->setContentsMargins(0, 8, 0, 8); + FlowLayout* flowLayout = new FlowLayout(0, 4, 4); ui->usersWidget->setLayout(flowLayout); ui->messagePlainTextEdit->installEventFilter(this); @@ -61,8 +73,11 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { 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(); + QString message = ui->messagePlainTextEdit->document()->toPlainText(); + if (!message.trimmed().isEmpty()) { + _xmppClient.sendMessage(_chatRoom->jid(), message); + ui->messagePlainTextEdit->document()->clear(); + } return true; } return false; @@ -72,6 +87,26 @@ QString ChatWindow::getParticipantName(const QString& participant) { return participant.right(participant.count() - 1 - _chatRoom->jid().count()); } +void ChatWindow::addTimeStamp() { + QTimeSpan timePassed = QDateTime::currentDateTime() - lastMessageStamp; + int times[] = { timePassed.daysPart(), timePassed.hoursPart(), timePassed.minutesPart() }; + QString strings[] = { tr("day", 0, times[0]), tr("hour", 0, times[1]), tr("minute", 0, times[2]) }; + QString timeString = ""; + for (int i = 0; i < 3; i++) { + if (times[i] > 0) { + timeString += strings[i] + " "; + } + } + timeString.chop(1); + QLabel* timeLabel = new QLabel(timeString); + timeLabel->setStyleSheet("color: palette(shadow);" + "background-color: palette(highlight);" + "padding: 4px;"); + timeLabel->setAlignment(Qt::AlignHCenter); + ui->messagesFormLayout->addRow(timeLabel); + numMessagesAfterLastTimeStamp = 0; +} + void ChatWindow::connected() { _chatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); connect(_chatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); @@ -83,6 +118,17 @@ void ChatWindow::connected() { ui->usersWidget->show(); ui->messagesScrollArea->show(); ui->messagePlainTextEdit->show(); + + QTimer* timer = new QTimer(this); + timer->setInterval(10 * 60 * 1000); + connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); + timer->start(); +} + +void ChatWindow::timeout() { + if (numMessagesAfterLastTimeStamp >= NUM_MESSAGES_TO_TIME_STAMP) { + addTimeStamp(); + } } void ChatWindow::error(QXmppClient::Error error) { @@ -98,13 +144,12 @@ void ChatWindow::participantsChanged() { } 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"); + 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); } } @@ -115,15 +160,25 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { font.setBold(true); userLabel->setFont(font); userLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + userLabel->setStyleSheet("padding: 4px;"); QLabel* messageLabel = new QLabel(message.body().replace(regexLinks, "\\1")); messageLabel->setWordWrap(true); messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); messageLabel->setOpenExternalLinks(true); + messageLabel->setStyleSheet("padding: 4px;"); ui->messagesFormLayout->addRow(userLabel, messageLabel); ui->messagesFormLayout->parentWidget()->updateGeometry(); Application::processEvents(); QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); + + ++numMessagesAfterLastTimeStamp; + if (message.stamp().isValid()) { + lastMessageStamp = message.stamp().toLocalTime(); + } + else { + lastMessageStamp = QDateTime::currentDateTime(); + } } diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index c1963b5342..afa1947268 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -1,7 +1,17 @@ +// +// ChatWindow.h +// interface +// +// Created by Dimitar Dobrev on 3/6/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + #ifndef __interface__ChatWindow__ #define __interface__ChatWindow__ #include +#include +#include #include @@ -23,15 +33,19 @@ protected: bool eventFilter(QObject* sender, QEvent* event); private: + QString getParticipantName(const QString& participant); + void addTimeStamp(); + Ui::ChatWindow* ui; QXmppClient _xmppClient; QXmppMucManager _xmppMUCManager; QXmppMucRoom* _chatRoom; - - QString getParticipantName(const QString& participant); + int numMessagesAfterLastTimeStamp; + QDateTime lastMessageStamp; private slots: void connected(); + void timeout(); void error(QXmppClient::Error error); void participantsChanged(); void messageReceived(const QXmppMessage& message); diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index 554b42833e..1723e72665 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -54,7 +54,7 @@ - font-weight: bold; color: palette(shadow) + font-weight: bold; color: palette(shadow); margin-bottom: 4px; online now: @@ -66,6 +66,12 @@ + + margin-top: 12px; + + + Qt::ScrollBarAlwaysOff + true @@ -74,16 +80,19 @@ 0 0 - 382 - 356 + 358 + 328 + + margin-top: 0px; + QFormLayout::AllNonFixedFieldsGrow - 4 + 0 4 @@ -112,6 +121,9 @@ 0 + + Qt::ScrollBarAlwaysOff + diff --git a/libraries/shared/src/qtimespan.cpp b/libraries/shared/src/qtimespan.cpp new file mode 100644 index 0000000000..2ddddb30f3 --- /dev/null +++ b/libraries/shared/src/qtimespan.cpp @@ -0,0 +1,2290 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +//#include "private/qdatetime_p.h" +#include +#include +#include +#include "qdatastream.h" +#include "qlocale.h" +#include "qtimespan.h" +#include "qdebug.h" +#include "qcoreapplication.h" +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) +#include +#endif +#ifndef Q_WS_WIN +#include +#endif + +#include +#if defined(Q_OS_WINCE) +#include "qfunctions_wince.h" +#endif + +#if defined(Q_WS_MAC) +#include +#endif + +#if defined(Q_OS_SYMBIAN) +#include +#endif + + + +/*! + \class QTimeSpan + \brief The QTimeSpan represents a span of time + \since 4.8 + + QTimeSpan represents a span of time, which is optionally in reference to a specific + point in time. A QTimeSpan behaves slightly different if it has a reference date or time + or not. + + \section1 Constructing a QTimeSpan + A QTimeSpan can be created by initializing it directly with a length and optionally with + a reference (start) date, or by substracting two QDate or QDateTime values. By substracting + QDate or QDateTime values, you create a QTimeSpan with the QDate or QDateTime on the right + hand side of the - operator as the reference date. + + \code + //Creates a QTimeSpan representing the time from October 10, 1975 to now + QDate birthDay(1975, 10, 10); + QTimeSpan age = QDate::currentDate() - birthDay; + \endcode + + QTimeSpan defines a series of constants that can be used for initializing a QTimeSpan. + Second, Minute, Hour, Day and Week are all QTimeSpan instances with the corresponding + length and no reference date. You can use those to create new instances. See the + section on Date arithmatic below. + + \code + //Creates a QTimeSpan representing 2 days, 4 hours and 31 minutes. + QTimeSpan span(2 * QTimeSpan::Day + 4 * QTimeSpan::Hour + 31 * QTimeSpan::Minute); + \endcode + + Finally, a QTimeSpan can be constructed by using one of the static constructors + fromString or fromTimeUnit. + + \section1 Date arithmatic + A negative QTimeSpan means that the reference date lies before the referenced date. Call + normalize() to ensure that the reference date is smaller or equal than the referenced date. + + Basic arithmatic can be done with QTimeSpan. QTimeSpans can be added up or substracted, or + be multiplied by a scalar factor. For this, the usual operators are implemented. The union + of QTimeSpans will yield the minimal QTimeSpan that covers both the original QTimeSpans, + while the intersection will yield the overlap between them (or an empty one if there is no + overlap). Please refer to the method documentation for details on what happens to a + reference date when using these methods. + + QTimeSpans can also be added to or substracted from a QDate, QTime or QDateTime. This will yield + a new QDate, QTime or QDateTime moved by the value of the QTimeSpan. Note that the QTimeSpan must + be the right-hand argument of the operator. You can not add a QDate to a QTimeSpan, but you + can do the reverse. + + \code + QTimeSpan span(QTimeSpan::Hour * 5 + 45 * QTimeSpan::Minute); + QDateTime t1 = QDateTime::currentDateTime(); + QDateTime t2 = t1 + span; // t2 is now the date time 5 hours and 45 minutes in the future. + \endcode + + \section1 Accessing the length of a QTimeSpan + There are two sets of methods that return the length of a QTimeSpan. The to* methods such + as toSeconds and toMinutes return the total time in the requested unit. That may be a + fractional number. + + \code + QTimeSpan span = QTimeSpan::Hour * 6; + qreal days = span.toDays(); //yields 0.25 + \endcode + + On the other hand, you may be interested in a number of units at the same time. If you want + to know the number of days, hours and minutes in a QTimeSpan, you can use the to*Part + methods such as toDayPart and toHourPart. These functions take a QTimeSpan::TimeSpanFormat + argument to indicate the units you want to use for the presentation of the QTimeSpan. + This is used to calculate the number of the requested time units. You can also use the + parts method directly, passing pointers to ints for the units you are interested in and 0 + for the other units. + + \section1 Using months and years + QTimeSpan can be used to describe any length of time, ranging from milliseconds to decades and + beyond (up to the maximum value of a qint64 milliseconds; enough for any application not + dealing with geological or astronomical time scales.) It is natural to use units like months + and years when dealing with longer time periods, such as the age of people. The problem with + these units is that unlike the time units for a week or shorter, the length of a month or a + year is not fixed. It it dependent on the reference date. The time period '1 month' has a + different meaning when we are speaking of februari or januari. + + QTimeSpan can only use the month and year time units if a valid reference date has been + set. Without a valid reference date, month and year as time units are meaningless and their + use will result in an assert. The largest unit of time that can be expressed without a reference + date is a week. The time period of one month is understood to mean the period + from a day and time one month to the same date and time in the next. If the next month does + not have that date and time, one month will be taken to mean the period to the end of that + month. + + \example The time from januari 2, 12 PM to februari 2, 12 PM will be understood as exactly one month. + \example The time from januari 30, 2 PM to march 1, 00:00:00.000 will also be one month, because + februari does not have 30 days but 28 or 29 depending on the year. + \example The time from januari 30, 2 PM to march 30, 2 PM will be 2 months. + + The same goes for years. + + QTimeSpan stores the length of time intervals as a 64 bits integer representing milliseconds. + That means that arithmatic with time periods set as months or years may not always yield what you + expect. A time period set as the year describing the whole of 2007 that you multiply by two or + add to itself, will not end up having a length of two years, but of 1 year, 11 months and 30 + days, as 2008 is one day longer than 2007. When months and years are used, they are + converted to the exact time span they describe in reference to the reference date set for + the QTimeSpan. With another reference date, or when negated, that time span may or may not + describe the same number of years and months. +*/ + +QT_BEGIN_NAMESPACE + +const QTimeSpan QTimeSpan::Second = QTimeSpan(qint64(1000)); +const QTimeSpan QTimeSpan::Minute = QTimeSpan(qint64(1000 * 60)); +const QTimeSpan QTimeSpan::Hour = QTimeSpan(qint64(1000 * 60 * 60)); +const QTimeSpan QTimeSpan::Day = QTimeSpan(qint64(1000 * 60 * 60 * 24)); +const QTimeSpan QTimeSpan::Week = QTimeSpan(qint64(1000 * 60 * 60 * 24 * 7)); + +class QTimeSpanPrivate : public QSharedData { +public: + qint64 interval; + QDateTime reference; + static const int s_daysPerMonth[12]; + + void addUnit(QTimeSpan* self, Qt::TimeSpanUnit unit, qreal value) + { + if (unit >= Qt::Months) { + QTimeSpan tempSpan(self->referencedDate()); + tempSpan.setFromTimeUnit(unit, value); + interval += tempSpan.toMSecs(); + } else { + switch (unit) { + case Qt::Weeks: + interval += value * 1000LL * 60LL * 60LL * 24LL * 7LL; + break; + case Qt::Days: + interval += value * 1000LL * 60LL * 60LL * 24LL; + break; + case Qt::Hours: + interval += value * 1000LL * 60LL * 60LL ; + break; + case Qt::Minutes: + interval += value * 1000LL * 60LL; + break; + case Qt::Seconds: + interval += value * 1000LL; + break; + case Qt::Milliseconds: + interval += value; + break; + default: + break; + } + } + } + + class TimePartHash: public QHash + { + public: + TimePartHash(Qt::TimeSpanFormat format) + { + for (int i(Qt::Milliseconds); i <= Qt::Years; i *= 2) { + Qt::TimeSpanUnit u = static_cast(i); + if (format.testFlag(u)) { + int* newValue = new int; + *newValue = 0; + insert(u, newValue); //perhaps we can optimize this not to new each int individually? + + } else { + insert(u, 0); + } + } + } + + ~TimePartHash() + { + qDeleteAll(*this); + } + + inline bool fill(const QTimeSpan& span) + { + bool result = span.parts(value(Qt::Milliseconds), + value(Qt::Seconds), + value(Qt::Minutes), + value(Qt::Hours), + value(Qt::Days), + value(Qt::Weeks), + value(Qt::Months), + value(Qt::Years)); + return result; + } + + inline void addUnit(const Qt::TimeSpanUnit unit) + { + if (value(unit) != 0) + return; + + int* newValue = new int; + *newValue = 0; + insert(unit, newValue); + } + + }; + + //returns a string representation of time in a single time unit + QString unitString(Qt::TimeSpanUnit unit, int num) const + { + switch (unit) { + case::Qt::Milliseconds: + return qApp->translate("QTimeSpanPrivate", "%n millisecond(s)", 0, num); + case::Qt::Seconds: + return qApp->translate("QTimeSpanPrivate", "%n second(s)", 0, num); + case::Qt::Minutes: + return qApp->translate("QTimeSpanPrivate", "%n minute(s)", 0, num); + case::Qt::Hours: + return qApp->translate("QTimeSpanPrivate", "%n hour(s)", 0, num); + case::Qt::Days: + return qApp->translate("QTimeSpanPrivate", "%n day(s)", 0, num); + case::Qt::Weeks: + return qApp->translate("QTimeSpanPrivate", "%n week(s)", 0, num); + case::Qt::Months: + return qApp->translate("QTimeSpanPrivate", "%n month(s)", 0, num); + case::Qt::Years: + return qApp->translate("QTimeSpanPrivate", "%n year(s)", 0, num); + default: + return QString(); + } + } + +#ifndef QT_NO_DATESTRING + struct TimeFormatToken + { + Qt::TimeSpanUnit type; //Qt::NoUnit is used for string literal types + int length; //number of characters to use + QString string; //only used for string literals + }; + + QList parseFormatString(const QString& formatString, Qt::TimeSpanFormat &format) const + { + QHash tokenHash; + tokenHash.insert(QChar('y'), Qt::Years); + tokenHash.insert(QChar('M'), Qt::Months); + tokenHash.insert(QChar('w'), Qt::Weeks); + tokenHash.insert(QChar('d'), Qt::Days); + tokenHash.insert(QChar('h'), Qt::Hours); + tokenHash.insert(QChar('m'), Qt::Minutes); + tokenHash.insert(QChar('s'), Qt::Seconds); + tokenHash.insert(QChar('z'), Qt::Milliseconds); + + + QList tokenList; + format = Qt::NoUnit; + int pos(0); + int length(formatString.length()); + bool inLiteral(false); + while (pos < length) { + const QChar currentChar(formatString[pos]); + if (inLiteral) { + if (currentChar == QLatin1Char('\'')) { + inLiteral = false; //exit literal string mode + if ((pos+1)interval = 0; +} + +/*! + \brief Constructor + + Constructs QTimeSpan of size msecs milliseconds. The reference date will + be invalid. +*/ +QTimeSpan::QTimeSpan(qint64 msecs) + : d(new QTimeSpanPrivate) +{ + d->interval = msecs; +} + +/*! + \brief Copy Constructor +*/ +QTimeSpan::QTimeSpan(const QTimeSpan& other): + d(other.d) +{ +} + +/*! + \brief Constructor + + Constructs QTimeSpan of size msecs milliseconds from the given reference date + and time. +*/ +QTimeSpan::QTimeSpan(const QDateTime &reference, qint64 msecs) + : d(new QTimeSpanPrivate) +{ + d->interval = msecs; + d->reference = reference; +} + +/*! + \brief Constructor + + Constructs QTimeSpan of size msecs milliseconds from the given reference date. + The reference time will be 0:00:00.000 +*/ +QTimeSpan::QTimeSpan(const QDate &reference, quint64 msecs) + : d(new QTimeSpanPrivate) +{ + d->interval = msecs; + d->reference = QDateTime(reference); +} + +/*! + \brief Constructor + + Constructs QTimeSpan of size msecs milliseconds from the given reference time. + The reference date will be today's date. +*/ +QTimeSpan::QTimeSpan(const QTime &reference, quint64 msecs) + : d(new QTimeSpanPrivate) +{ + d->interval = msecs; + QDateTime todayReference(QDate::currentDate()); + todayReference.setTime(reference); + d->reference = todayReference; +} + +/*! + \brief Constructor + + Constructs a QTimeSpan of the same length as other from the given reference date time. +*/ +QTimeSpan::QTimeSpan(const QDateTime& reference, const QTimeSpan& other) + : d(new QTimeSpanPrivate) +{ + d->reference = reference; + d->interval = other.d->interval; +} + +/*! + \brief Constructor + + Constructs a QTimeSpan of the same length as other from the given reference date. + The reference time will be 00:00:00.000 +*/ +QTimeSpan::QTimeSpan(const QDate& reference, const QTimeSpan& other) + : d(new QTimeSpanPrivate) +{ + d->reference = QDateTime(reference); + d->interval = other.d->interval; +} + +/*! + \brief Constructor + + Constructs a QTimeSpan of the same length as other from the given reference time. + The reference date will be today's date. +*/ +QTimeSpan::QTimeSpan(const QTime& reference, const QTimeSpan& other) + : d(new QTimeSpanPrivate) +{ + QDateTime todayReference(QDate::currentDate()); + todayReference.setTime(reference); + d->reference = todayReference; + d->interval = other.d->interval; +} + + +/*! + \brief Destructor +*/ +QTimeSpan::~QTimeSpan() +{ +} + +/*! + \returns true if the time span is 0; that is, if no time is spanned by + this instance. There may or may not be a valid reference date. + + \sa isNull hasValidReference +*/ +bool QTimeSpan::isEmpty() const +{ + return d->interval == 0; +} + +/*! + \returns true if the time span is 0; that is, if no time is spanned by + this instance and there is no valid reference date. + + \sa isEmpty +*/ +bool QTimeSpan::isNull() const +{ + return isEmpty() && (!hasValidReference()); +} + +/*! + \brief Assignment operator +*/ +QTimeSpan& QTimeSpan::operator=(const QTimeSpan& other) { + if (&other == this) + return *this; + + d = other.d; + return *this; +} + +/*! + \returns a new QTimeSpan instance initialized to the indicated number of + time units. The default reference date is invalid. + + \note that you can only construct a valid QTimeSpan using the Months or Years + time units if you supply a valid reference date. + + \sa setFromTimeUnit +*/ +QTimeSpan QTimeSpan::fromTimeUnit(Qt::TimeSpanUnit unit, qreal interval, const QDateTime& reference ) +{ + switch (unit){ //note: fall through is intentional! + case Qt::Weeks: + interval *= 7.0; + case Qt::Days: + interval *= 24.0; + case Qt::Hours: + interval *= 60.0; + case Qt::Minutes: + interval *= 60.0; + case Qt::Seconds: + interval *= 1000.0; + case Qt::Milliseconds: + break; + default: + if (reference.isValid()) { + QTimeSpan result(reference); + result.setFromTimeUnit(unit, interval); + return result; + } + Q_ASSERT_X(false, "static constructor", "Can not construct QTimeSpan from Month or Year TimeSpanUnit without a valid reference date."); + return QTimeSpan(); + } + + return QTimeSpan(reference, qint64(interval)); +} + +/*! + \returns the number of the requested units indicated by unit when formatted + as format. + + \sa parts() +*/ +int QTimeSpan::part(Qt::TimeSpanUnit unit, Qt::TimeSpanFormat format) const +{ + if (!format.testFlag(unit)) + return 0; + + if (!hasValidReference()) { + if (unit == Qt::Months || unit == Qt::Years) { + Q_ASSERT_X(false, "part", "Can not calculate Month or Year part without a reference date"); + } + if (format.testFlag(Qt::Months) || format.testFlag(Qt::Years)) { + qWarning() << "Unsetting Qt::Months and Qt::Years flags from format. Not supported without a reference date"; + //should this assert instead? + format&= (Qt::AllUnits ^ (Qt::Months | Qt::Years)); + } + } + + //build up hash with pointers to ints for the units that are set in format, and 0's for those that are not. + QTimeSpanPrivate::TimePartHash partsHash(format); + bool result = partsHash.fill(*this); + + if (!result) { + //what to do? Assert perhaps? + qWarning() << "Result is invalid!"; + return 0; + } + + int val = *(partsHash.value(unit)); + return val; +} + +#define CHECK_INT_LIMIT(interval, unitFactor) if (interval >= (qint64(unitFactor) * qint64(std::numeric_limits::max()) ) ) {qWarning() << "out of range" << unitFactor; return false;} + +/*! + Retreives a breakup of the length of the QTimeSpan in different time units. + + While part() allows you to retreive the value of a single unit for a specific + representation of time, this method allows you to retreive all these values + with a single call. The units that you want to use in the representation of the + time span is defined implicitly by the pointers you pass. Passing a valid pointer + for a time unit will include that unit in the representation, while passing 0 + for that pointer will exclude it. + + The passed integer pointers will be set to the correct value so that together + they represent the whole time span. This function will then return true. + If it is impossible to represent the whole time span in the requested units, + this function returns false. + + The fractionalSmallestUnit qreal pointer can optionally be passed in to + retreive the value for the smallest time unit passed in as a fractional number. + For instance, if your time span contains 4 minutes and 30 seconds, but the + smallest time unit you pass in an integer pointer for is the minute unit, then + the minute integer will be set to 4 and the fractionalSmallestUnit will be set + to 4.5. + + A negative QTimeSpan will result in all the parts of the representation to be + negative, while a positive QTimeSpan will result in an all positive + representation. + + \note Months and years are only valid as units for time spans that have a valid + reference date. Requesting the number of months or years for time spans without + a valid reference date will return false. + + If this function returns false, the value of the passed in pointers is undefined. + + \sa part() +*/ +bool QTimeSpan::parts(int *msecondsPtr, + int *secondsPtr, + int *minutesPtr, + int *hoursPtr, + int *daysPtr, + int *weeksPtr, + int *monthsPtr, + int *yearsPtr, + qreal *fractionalSmallestUnit) const +{ + /* \todo We should probably cache the results of this operation. However, that requires keeping a dirty flag + in the private data store, or a copy of the reference date, interval and last used parts. Is that worth it? + */ + + // Has the user asked for a fractional component? If yes, find which unit it corresponds to. + Qt::TimeSpanUnit smallestUnit = Qt::NoUnit; + if (fractionalSmallestUnit) + { + if (yearsPtr) + smallestUnit = Qt::Years; + if (monthsPtr) + smallestUnit = Qt::Months; + if (weeksPtr) + smallestUnit = Qt::Weeks; + if (daysPtr) + smallestUnit = Qt::Days; + if (hoursPtr) + smallestUnit = Qt::Hours; + if (minutesPtr) + smallestUnit = Qt::Minutes; + if (secondsPtr) + smallestUnit = Qt::Seconds; + if (msecondsPtr) + smallestUnit = Qt::Milliseconds; + } + + QTimeSpan ts(*this); + if (yearsPtr || monthsPtr) { //deal with months and years + //we can not deal with months or years if there is no valid reference date + if (!hasValidReference()) { + qWarning() << "Can not request month or year parts of a QTimeSpan without a valid reference date."; + return false; + } + + int* _years = yearsPtr; + if (!yearsPtr) + _years = new int; + + *_years = 0; + QDate startDate = ts.startDate().date(); + QDate endDate = ts.endDate().date(); + *_years = endDate.year() - startDate.year(); + if (endDate.month() < startDate.month()) { + (*_years)--; + } else if (endDate.month() == startDate.month()) { + if (endDate.day() < startDate.day()) { + (*_years)--; + } + } + + /** \todo Handle fractional years component */ + + int* _months = monthsPtr; + if (!monthsPtr) + _months = new int; + + *_months = endDate.month() - startDate.month(); + if (*_months < 0) + (*_months) += 12; + if (endDate.day() < startDate.day()) { + (*_months)--; + } + int totalMonths = (*_months); + if (!yearsPtr) + totalMonths += (*_years) * 12; + + QDate newStartDate(startDate); + newStartDate = newStartDate.addYears(*_years); + newStartDate = newStartDate.addMonths(*_months); + //qDebug() << "working with new start date" << newStartDate << "and end date" << endDate; + ts = QDateTime(endDate, ts.endDate().time()) - QDateTime(newStartDate, ts.startDate().time()); + *_months = totalMonths; + + /** \todo Handle fractional months component */ + + //clean up temporary variables on the heap + if (!monthsPtr) + delete _months; + if (!yearsPtr) + delete _years; + } + + //from here on, we use ts as the time span! + qint64 intervalLeft = ts.toMSecs(); + qint64 unitFactor; + //qDebug() << "intervalLeft" << intervalLeft; + if (weeksPtr) { + unitFactor = (7 * 24 * 60 * 60 * 1000); + CHECK_INT_LIMIT(intervalLeft, unitFactor) + + *weeksPtr = intervalLeft / unitFactor; + if (smallestUnit == Qt::Weeks) + { + QTimeSpan leftOverTime(referencedDate(), -intervalLeft); + leftOverTime.normalize(); + *fractionalSmallestUnit = leftOverTime.toTimeUnit(smallestUnit); + } + + if (*weeksPtr != 0) { + intervalLeft = intervalLeft % unitFactor; + } + } + + if (daysPtr) { + unitFactor = (24 * 60 * 60 * 1000); + CHECK_INT_LIMIT(intervalLeft, unitFactor) + + *daysPtr = intervalLeft / unitFactor; + if (smallestUnit == Qt::Days) + { + QTimeSpan leftOverTime(referencedDate(), -intervalLeft); + leftOverTime.normalize(); + *fractionalSmallestUnit = leftOverTime.toTimeUnit(smallestUnit); + } + + if (*daysPtr != 0 ) { + intervalLeft = intervalLeft % unitFactor; + } + } + + if (hoursPtr) { + unitFactor = (60 * 60 * 1000); + CHECK_INT_LIMIT(intervalLeft, unitFactor) + + *hoursPtr = intervalLeft / unitFactor; + if (smallestUnit == Qt::Hours) + { + QTimeSpan leftOverTime(referencedDate(), -intervalLeft); + leftOverTime.normalize(); + *fractionalSmallestUnit = leftOverTime.toTimeUnit(smallestUnit); + } + + if (*hoursPtr != 0 ) { + intervalLeft = intervalLeft % unitFactor; + } + } + + if (minutesPtr) { + unitFactor = (60 * 1000); + CHECK_INT_LIMIT(intervalLeft, unitFactor) + + *minutesPtr = intervalLeft / unitFactor; + if (smallestUnit == Qt::Minutes) + { + QTimeSpan leftOverTime(referencedDate(), -intervalLeft); + leftOverTime.normalize(); + *fractionalSmallestUnit = leftOverTime.toTimeUnit(smallestUnit); + } + + if (*minutesPtr != 0 ) { + intervalLeft = intervalLeft % unitFactor; + } + } + + if (secondsPtr) { + unitFactor = (1000); + CHECK_INT_LIMIT(intervalLeft, unitFactor) + + *secondsPtr = intervalLeft / unitFactor; + if (smallestUnit == Qt::Seconds) + { + QTimeSpan leftOverTime(referencedDate(), -intervalLeft); + leftOverTime.normalize(); + *fractionalSmallestUnit = leftOverTime.toTimeUnit(smallestUnit); + } + + if (*secondsPtr > 0 ) { + intervalLeft = intervalLeft % unitFactor; + } + } + + if (msecondsPtr) { + unitFactor = 1; + CHECK_INT_LIMIT(intervalLeft, unitFactor) + + *msecondsPtr = intervalLeft; + if (smallestUnit == Qt::Weeks) + { + *fractionalSmallestUnit = qreal(intervalLeft); + } + } + + return true; +} + +/*! + Sets a part of the time span in the given format. + + setPart allows you to adapt the current time span interval unit-by-unit based on + any time format. Where setFromTimeUnit resets the complete time interval, setPart + only sets a specific part in a chosen format. + + \example If you have a time span representing 3 weeks, 2 days, 4 hours, 31 minutes + and 12 seconds, you can change the number of hours to 2 by just calling + span.setPart(Qt::Hours, 2, Qt::Weeks | Qt::Days | Qt::Hours | Qt::Minutes | Qt::Seconds); + + Note that just like with any other function, you can not use the Months and Years + units without using a reference date. +*/ +void QTimeSpan::setPart(Qt::TimeSpanUnit unit, int interval, Qt::TimeSpanFormat format) +{ + if (!format.testFlag(unit)) { + qWarning() << "Can not set a unit that is not part of the format. Ignoring."; + return; + } + + QTimeSpanPrivate::TimePartHash partsHash(format); + bool result = partsHash.fill(*this); + + if (!result) { + qWarning() << "Retreiving parts failed, cannot set parts. Ignoring."; + return; + } + + d->addUnit(this, unit, interval - *(partsHash.value(unit) ) ); +} + +/*! + Returns Qt::TimeSpanUnit representing the order of magnitude of the time span. + That is, the largest unit that can be used to display the time span that + will result in a non-zero value. + + If the QTimeSpan does not have a valid reference date, the largest + possible time unit that will be returned is Qt::Weeks. Otherwise, + the largest possible time unit is Qt::Years. + + \returns Unit representing the order of magnitude of the time span. +*/ +Qt::TimeSpanUnit QTimeSpan::magnitude() +{ + //abs(qint64) doesnt't compile properly with mingw32? + qint64 mag = d->interval; + if (mag < 0) + mag = -mag; + + if (mag < 1000) + return Qt::Milliseconds; + if (mag < (1000LL * 60LL)) + return Qt::Seconds; + if (mag < (1000LL * 60LL * 60LL)) + return Qt::Minutes; + if (mag < (1000LL * 60LL * 60LL * 24LL)) + return Qt::Hours; + if (mag < (1000LL * 60LL * 60LL * 24LL * 7LL)) + return Qt::Days; + + //those the simple cases. The rest is dependent on if there is a reference date + if (hasValidReference()) { + //simple test. If bigger than 366 (not 365!) then we are certain of dealing with years + if (mag > (1000LL * 60LL * 60LL * 24LL * 366LL)) + return Qt::Years; + //we need a more complicated test + int years = 0; + int months = 0; + parts(0, 0, 0, 0, 0, 0, &months, &years); + if (years > 0) + return Qt::Years; + if (months > 0) + return Qt::Months; + } + + return Qt::Weeks; +} + +/*! + \returns true if there is a valid reference date set, false otherwise. +*/ +bool QTimeSpan::hasValidReference() const +{ + return d->reference.isValid(); +} + +/*! + \returns the reference date. Note that the reference date may be invalid. +*/ +QDateTime QTimeSpan::referenceDate() const +{ + return d->reference; +} + +/*! + Sets the reference date. + + If there currently is a reference date, the referenced date will + not be affected. That means that the length of the time span will + change. If there currently is no reference date set, the interval + will not be affected and this function will have the same + effect as moveReferenceDate. + + /sa moveReferenceDate setReferencedDate moveReferencedDate +*/ +void QTimeSpan::setReferenceDate(const QDateTime &referenceDate) +{ + if (d->reference.isValid() && referenceDate.isValid()) { + *this = referencedDate() - referenceDate; + } else { + d->reference = referenceDate; + } +} + +/*! + Moves the time span to align the time spans reference date with the + new reference date. + + Note that the length of the time span will not be modified, so the + referenced date will shift as well. If no reference date was set + before, it is set now and the referenced date will become valid. + + /sa setReferenceDate setReferencedDate moveReferencedDate +*/ +void QTimeSpan::moveReferenceDate(const QDateTime &referenceDate) +{ + d->reference = referenceDate; +} + +/*! + Sets the referenced date. + + If there currently is a reference date, that reference date will + not be affected. This implies that the length of the time span changes. + If there currently is no reference date set, the interval + will not be affected and this function will have the same + effect as moveReferencedDate. + + /sa setReferenceDate moveReferenceDate moveReferencedDate + +*/ +void QTimeSpan::setReferencedDate(const QDateTime &referencedDate) +{ + if (d->reference.isValid()) { + *this = referencedDate - d->reference; + } else { + d->reference = referencedDate.addMSecs(-(d->interval)); + } +} + +/*! + Moves the time span to align the time spans referenced date with the + new referenced date. + + Note that the length of the time span will not be modified, so the + reference date will shift as well. If no reference date was set + before, it is set now. + + /sa setReferenceDate setReferencedDate moveReferencedDate +*/ +void QTimeSpan::moveReferencedDate(const QDateTime &referencedDate) +{ + d->reference = referencedDate.addMSecs(-(d->interval)); +} + +/*! + Returns the referenced date and time. + + The referenced QDateTime is the "other end" of the QTimeSpan from + the reference date. + + An invalid QDateTime will be returned if no valid reference date + has been set. +*/ +QDateTime QTimeSpan::referencedDate() const +{ + if (!(d->reference.isValid())) + return QDateTime(); + + QDateTime dt(d->reference); + dt = dt.addMSecs(d->interval); + return dt; +} + +// Comparison operators +/*! + Returns true if the two compared QTimeSpans have both the same + reference date and the same length. + + Note that two QTimeSpan objects that span the same period, but + where one is positive and the other is negative are not considdered + equal. If you need to compare those, compare the normalized + versions. + + \sa matchesLength +*/ +bool QTimeSpan::operator==(const QTimeSpan &other) const +{ + return ((d->interval == other.d->interval) && + (d->reference == other.d->reference)); +} + +/*! + Returns true if the interval of this QTimeSpan is shorter than + the interval of the other QTimeSpan. +*/ +bool QTimeSpan::operator<(const QTimeSpan &other) const +{ + return d->interval < other.d->interval; +} + +/*! + Returns true if the interval of this QTimeSpan is shorter or equal + than the interval of the other QTimeSpan. +*/ +bool QTimeSpan::operator<=(const QTimeSpan &other) const +{ + return d->interval <= other.d->interval; +} + +/*! + Returns true if the interval of this QTimeSpan is equal + to the interval of the other QTimeSpan. That is, if they have the + same length. + + The default value of normalize is false. If normalize is true, the + absolute values of the interval lengths are compared instead of the + real values. + + \code + QTimeSpan s1 = 2 * QTimeSpan::Day; + QTimeSpan s2 = -2 * QTimeSpan::Day; + qDebug() << s1.matchesLength(s2); //returns false + qDebug() << s1.matchesLength(s2, true); //returns true + \endcode +*/ +bool QTimeSpan::matchesLength(const QTimeSpan &other, bool normalize) const +{ + if (!normalize) { + return d->interval == other.d->interval; + } else { + return qAbs(d->interval) == qAbs(other.d->interval); + } +} + +// Arithmatic operators +/*! + Adds the interval of the other QTimeSpan to the interval of + this QTimeSpan. The reference date of the other QTimeSpan is + ignored. +*/ +QTimeSpan & QTimeSpan::operator+=(const QTimeSpan &other) +{ + d->interval += other.d->interval; + return *this; +} + +/*! + Adds the number of milliseconds to the interval of + this QTimeSpan. The reference date of the QTimeSpan is + not affected. +*/ +QTimeSpan & QTimeSpan::operator+=(qint64 msecs) +{ + d->interval += msecs; + return *this; +} + +/*! + Substracts the interval of the other QTimeSpan from the interval of + this QTimeSpan. The reference date of the other QTimeSpan is + ignored while the reference date of this QTimeSpan is not affected. +*/ +QTimeSpan & QTimeSpan::operator-=(const QTimeSpan &other) +{ + d->interval -= (other.d->interval); + return *this; +} + +/*! + Substracts the number of milliseconds from the interval of + this QTimeSpan. The reference date of the QTimeSpan is + not affected. +*/ +QTimeSpan & QTimeSpan::operator-=(qint64 msecs) +{ + d->interval -= msecs; + return *this; +} + +/*! + Multiplies the interval described by this QTimeSpan by the + given factor. The reference date of the QTimeSpan is not + affected. +*/ +QTimeSpan & QTimeSpan::operator*=(qreal factor) +{ + d->interval *= factor; + return *this; +} + +/*! + Multiplies the interval described by this QTimeSpan by the + given factor. The reference date of the QTimeSpan is not + affected. +*/ +QTimeSpan & QTimeSpan::operator*=(int factor) +{ + d->interval *= factor; + return *this; +} + +/*! + Divides the interval described by this QTimeSpan by the + given factor. The reference date of the QTimeSpan is not + affected. +*/ +QTimeSpan & QTimeSpan::operator/=(qreal factor) +{ + d->interval /= factor; + return *this; +} + +/*! + Divides the interval described by this QTimeSpan by the + given factor. The reference date of the QTimeSpan is not + affected. +*/ +QTimeSpan & QTimeSpan::operator/=(int factor) +{ + d->interval /= factor; + return *this; +} + +/*! + \brief Modifies this QTimeSpan to be the union of two QTimeSpans. + + The union of two QTimeSpans is defined as the minimum QTimeSpan that + encloses both QTimeSpans. The QTimeSpans need not be overlapping. + + \warning Only works if both QTimeSpans have a valid reference date. + \sa operator&=(const QTimeSpan& other) + \sa operator|(const QTimeSpan& other) + \sa united(const QTimeSpan& other) +*/ +QTimeSpan& QTimeSpan::operator|=(const QTimeSpan& other) // Union +{ + Q_ASSERT_X((hasValidReference() && other.hasValidReference()), "assignment-or operator", "Both participating time spans need a valid reference date"); + + //do we need to check for self-assignment? + + QDateTime start = qMin(startDate(), other.startDate()); + QDateTime end = qMax(endDate(), other.endDate()); + + *this = end - start; + return *this; +} + +/*! + \brief Modifies this QTimeSpan to be the intersection of two QTimeSpans. + + The intersection of two QTimeSpans is defined as the maximum QTimeSpan that + both QTimeSpans have in common. If the QTimeSpans don't overlap, a null QTimeSpan + will be returned. The returned QTimeSpan will be positive. + + \warning Only works if both QTimeSpans have a valid reference date. + \sa operator&=(const QTimeSpan& other) + \sa operator&(const QTimeSpan& other) + \sa overlapped(const QTimeSpan& other) +*/ +QTimeSpan& QTimeSpan::operator&=(const QTimeSpan &other) // Intersection +{ + Q_ASSERT_X((hasValidReference() && other.hasValidReference()), "assignment-or operator", "Both participating time spans need a valid reference date"); + + //do we need to check for self-assignment? + + const QTimeSpan* first = this; + const QTimeSpan* last = &other; + if (other.startDate() < startDate()) { + first = &other; + last = this; + } + + //check if there is overlap at all. If not, reset the interval to 0 + if (!(first->endDate() > last->startDate()) ) { + d->interval = 0; + return *this; + } + + *this = qMin(first->endDate(), last->endDate()) - last->startDate(); + return *this; +} + +/*! + Returns true if this QTimeSpan overlaps with the other QTimeSpan. + If one of the QTimeSpans does not have a valid reference date, this + function returns false. +*/ +bool QTimeSpan::overlaps(const QTimeSpan &other) const +{ + if (!hasValidReference() || !other.hasValidReference()) + return false; + + const QTimeSpan* first = this; + const QTimeSpan* last = &other; + if (other.startDate() < startDate()) { + first = &other; + last = this; + } + + return (first->endDate() > last->startDate()); +} + +/*! + \brief Returns a new QTimeSpan that represents the intersection of the two QTimeSpans. + + The intersection of two QTimeSpans is defined as the maximum QTimeSpan that + both QTimeSpans have in common. If the QTimeSpans don't overlap, a null QTimeSpan + will be returned. Any valid returned QTimeSpan will be positive. + + \warning Only works if both QTimeSpans have a valid reference date. Will assert otherwise. + \sa operator&=(const QTimeSpan& other) + \sa operator&(const QTimeSpan& other) + +*/ +QTimeSpan QTimeSpan::overlapped(const QTimeSpan &other) const +{ + Q_ASSERT_X((hasValidReference() && other.hasValidReference()), "assignment-or operator", "Both participating time spans need a valid reference date"); + + const QTimeSpan* first = this; + const QTimeSpan* last = &other; + if (other.startDate() < startDate()) { + first = &other; + last = this; + } + + //check if there is overlap at all. If not, reset the interval to 0 + if (!(first->endDate() >= last->startDate()) ) { + return QTimeSpan(); + } + + return qMin(first->endDate(), last->endDate()) - last->startDate(); +} + +/*! + \brief Returns a new QTimeSpan that represents the union of two QTimeSpans. + + The union of two QTimeSpans is defined as the minimum QTimeSpan that + encloses both QTimeSpans. The QTimeSpans need not be overlapping. + + \warning Only works if both QTimeSpans have a valid reference date. Will assert otherwise. + \sa operator|=(const QTimeSpan& other) + \sa operator|(const QTimeSpan& other) +*/ +QTimeSpan QTimeSpan::united(const QTimeSpan &other) const +{ + Q_ASSERT_X((hasValidReference() && other.hasValidReference()), "assignment-or operator", "Both participating time spans need a valid reference date"); + + QDateTime start = qMin(startDate(), other.startDate()); + QDateTime end = qMax(endDate(), other.endDate()); + + return ( end - start ); +} + +/*! + Determines if the given dateTime lies within the time span. The begin + and end times are taken to be contained in the time span in this function. + + If the time span does not have a valid reference date, this function + returns false. +*/ +bool QTimeSpan::contains(const QDateTime &dateTime) const +{ + if (!hasValidReference()) + return false; + + return ((startDate() <= dateTime) && + (endDate()) >= dateTime); +} + +/*! + Determines if the given date lies within the time span. The begin + and end times are taken to be contained in the time span in this function. + Just like in the QTimeSpan constructors that take a QDate, the time will assumed + to be 00:00:00.000. + + If the time span does not have a valid reference date, this function + returns false. +*/ +bool QTimeSpan::contains(const QDate &date) const +{ + QDateTime dt(date); + return contains(dt); +} + +/*! + Determines if the given time lies within the time span. The begin + and end times are taken to be contained in the time span in this function. + Just like in the QTimeSpan constructors that take a QTime, the date + will be set to today. + + If the time span does not have a valid reference date, this function + returns false. +*/ +bool QTimeSpan::contains(const QTime &time) const +{ + QDateTime dt(QDate::currentDate()); + dt.setTime(time); + return contains(dt); +} + +/*! + Determines if the other QTimeSpan lies within this time span. Another + time span is contained if its start time is the same or later then the + start time of this time span, and its end time is the same or earlier + than the end time of this time span. + + If either time span does not have a valid reference date, this function + returns false. +*/ +bool QTimeSpan::contains(const QTimeSpan &other) const +{ + if (!(hasValidReference() && other.hasValidReference())) + return false; + + return ((startDate() <= other.startDate()) && + (endDate()) >= other.endDate()); +} + +/*! + Returns a new QTimeSpan representing the same time span as this QTimeSpan, but + that is guaranteed to be positive; that is, to have the reference date before or + equal to the referenced date. + + \sa normalize abs + */ +QTimeSpan QTimeSpan::normalized() const +{ + QTimeSpan ts(*this); + ts.normalize(); + return ts; +} + +/*! + Modifies this QTimeSpan to represent the same time span, but + that is guaranteed to be positive; that is, to have the reference date before or + equal to the referenced date. If there is no valid reference date, the interval + will just be made positive. + + \sa normalized abs + */ +void QTimeSpan::normalize() +{ + if (d->interval < 0) { + if (hasValidReference()) { + d->reference = referencedDate(); + } + d->interval = qAbs(d->interval); + } +} + +/*! + Returns a copy of this QTimeSpan that is guaranteed to be positive; that is, + to have the reference date before or equal to the referenced date. The reference + date is not modified. That implies that if there is a reference date, and the + QTimeSpan is negative, the returned QTimeSpan will describe the time span \i after + the reference date instead of the time span \i before. + + If there is no valid reference date, the interval will just be made positive. + + \sa normalize normalized + */ +QTimeSpan QTimeSpan::abs() const +{ + QTimeSpan result(*this); + result.d->interval = qAbs(result.d->interval); + + return result; +} + +/*! + Returns true if the interval is negative. + \sa isNormal + */ +bool QTimeSpan::isNegative() const +{ + return d->interval < 0; +} + +/*! + \fn QTimeSpan::isNormal() const + + Returns true if the interval is normal, that is: not negative. + \sa isNegative + */ + +/*! + Returns the first date of the spanned time period. If there is no valid + reference date, an invalid QDateTime will be returned. + */ +QDateTime QTimeSpan::startDate() const +{ + if (isNegative()) + return referencedDate(); + + return referenceDate(); +} + +/*! + Returns the last date of the spanned time period. If there is no valid + reference date, an invalid QDateTime will be returned. + */ +QDateTime QTimeSpan::endDate() const +{ + if (isNegative()) + return referenceDate(); + + return referencedDate(); +} + +/*! + Returns the duration of the QTimeSpan expressed in milliseconds. This + value may be negative. +*/ +qint64 QTimeSpan::toMSecs() const +{ + return d->interval; +} + +/*! + Returns the duration of the QTimeSpan expressed in the given TimeSpanUnit. This + value may be negative. +*/ +qreal QTimeSpan::toTimeUnit(Qt::TimeSpanUnit unit) const +{ + qreal interval = qreal(d->interval); + switch (unit){ //fall through is intentional + case Qt::Weeks: + interval /= 7.0; + case Qt::Days: + interval /= 24.0; + case Qt::Hours: + interval /= 60.0; + case Qt::Minutes: + interval /= 60.0; + case Qt::Seconds: + interval /= 1000.0; + case Qt::Milliseconds: + break; + default: + Q_ASSERT_X(hasValidReference(), "toTimeUnit", "Can not convert to time units that depend on the reference date (month and year)."); + qreal result(0.0); + int intResult(0); + bool succes(false); + if (unit == Qt::Months) { + succes = parts(0, 0, 0, 0, 0, 0, &intResult, 0, &result); + } else if (unit == Qt::Years) { + succes = parts(0, 0, 0, 0, 0, 0, 0, &intResult, &result); + } + + if (!succes) + return 0.0; + + return result; + } + + return interval; +} + +/*! + Sets the length of this QTimeSpan from the given number of milliseconds. The reference date is not + affected. + */ +void QTimeSpan::setFromMSecs(qint64 msecs) +{ + d->interval = msecs; +} + +/*! + Sets the length of this QTimeSpan from the given number of TimeSpanUnits. The reference date is not + affected. + */ +void QTimeSpan::setFromTimeUnit(Qt::TimeSpanUnit unit, qreal interval) +{ + switch (unit){ + case Qt::Weeks: //fall through of cases is intentional! + interval *= 7.0; + case Qt::Days: + interval *= 24.0; + case Qt::Hours: + interval *= 60.0; + case Qt::Minutes: + interval *= 60.0; + case Qt::Seconds: + interval *= 1000.0; + case Qt::Milliseconds: + break; + case Qt::Months: + setFromMonths(interval); + return; + case Qt::Years: + setFromYears(interval); + return; + default: + Q_ASSERT_X(false, "setFromTimeUnit", "Can not set a QTimeSpan duration from unknown TimeSpanUnit."); + } + + d->interval = qint64(interval); +} + +/*! + Sets the interval of the time span as a number of months. + + \warning This function can only be used if a valid reference date has been set! + + The setFromMonths method deals with fractional months in the following way: first, + the whole number of months is extracted and added to the reference date. The fractional + part of the number of months is then multiplied with the number of days in the month + in which that date falls. If the number of months is negative and adding a whole month + yields a date exactly on a month boundary, the number of days in the month before is + used instead. + That number is used as the number of days and is added to the interval. + + \code + QTimeSpan ts(QDate(2010,01,01)); + ts.setFromMonths(1.5); // ts's referenced date is now februari 14, 0:00:00 + ts.setFromMonths(2.5); // ts's referenced date is now march 15, 0:00:00 + QTimeSpan ts2(QDate(2008,01,01)); //2008 is a leap year! + ts2.setFromMonths(1.5); // ts2's referenced date is now februari 14, 12:00:00 + QTimeSpan ts3(QDate(2008,03,01)); //2008 is a leap year + ts3.setFromMonths(-0.5); // ts3's referenced date is now februari 14: 12:00:00 // + \endcode + */ +void QTimeSpan::setFromMonths(qreal months) +{ + Q_ASSERT_X(hasValidReference(), "setFromMonths", "Can not set interval from time unit month if there is no reference date."); + + int fullMonths = int(months); + qreal fractionalMonth = months - fullMonths; + + QDateTime endDate = d->reference; + endDate = endDate.addMonths(fullMonths); + + int days = d->s_daysPerMonth[endDate.date().month()-1]; + QDateTime measureDate(endDate); + if (fractionalMonth < 0) { + measureDate = measureDate.addMSecs(-1); + } + if (QDate::isLeapYear(measureDate.date().year()) && measureDate.date().month() == 2) { + ++days; //februari has an extra day this year... + } + + QTimeSpan tmp = endDate - d->reference; + qreal fractionalDays = fractionalMonth * days; + d->interval = tmp.toMSecs() + qint64(fractionalDays * 24.0 * 60.0 * 60.0 * 1000.0); +} + +/*! + Sets the interval of the time span as a number of years. + + \warning This function can only be used if a valid reference date has been set! + + The setFromYears method deals with fractional years in the following way: first, + the whole number of years is extracted and added to the reference date. The fractional + part of the number of years is then multiplied with the number of days in the year + in which that date falls. That number is used as the number of days and is added to the + interval. + If the number of years is negative and adding the whole years yields a date exactly on + a year boundary, the number of days in the year before is used instead. + */ +void QTimeSpan::setFromYears(qreal years) +{ + Q_ASSERT_X(hasValidReference(), "setFromYears", "Can not set interval from time unit year if there is no reference date."); + + int fullYears = int(years); + qreal fractionalYear = years - fullYears; + + QDateTime endDate = d->reference; + endDate = endDate.addYears(fullYears); + + qreal days = 365.0; + QDateTime measureDate(endDate); + if (fractionalYear < 0) { + measureDate = measureDate.addMSecs(-1); + } + if (QDate::isLeapYear(measureDate.date().year())) { + days += 1.0; //februari has an extra day this year... + } + + QTimeSpan tmp = endDate - d->reference; + qreal fractionalDays = fractionalYear * days; + d->interval = tmp.toMSecs() + qint64(fractionalDays * 24.0 * 60.0 * 60.0 * 1000.0); +} + +#ifndef QT_NO_DATASTREAM +/*! + Streaming operator. + + This operator allows you to stream a QTimeSpan into a QDataStream. + /sa operator>>(QDataStream &stream, QTimeSpan &span) + */ +QDataStream & operator<<(QDataStream &stream, const QTimeSpan & span) +{ + stream << span.d->reference << span.d->interval; + return stream; +} + +/*! + Streaming operator. + + This operator allows you to stream a QTimeSpan out of a QDataStream. + /sa operator>>(QDataStream &stream, QTimeSpan &span) + */ +QDataStream & operator>>(QDataStream &stream, QTimeSpan &span) +{ + stream >> span.d->reference >> span.d->interval; + return stream; +} +#endif + +/*! + Adds another QTimeSpan to this QTimeSpan. + + The values of the intervals of the QTimeSpans are added up with normal + arithmatic. Negative values will work as expected. + + If the left argument has a reference date, that reference will be kept. + If only the right argument has a reference date, then that reference + date will be used as the new reference date. + + The above can have suprising consequences: + \code + // s1 and s2 are two QTimeSpan objects + QTimeSpan s12 = s1 + s2; + QTimeSpan s21 = s2 + s1; + + if (s12 == s21) { + //may or may not happen, depending on the reference dates of s1 and s2. + } + \endcode +*/ +QTimeSpan operator+(const QTimeSpan &left, const QTimeSpan &right) +{ + QTimeSpan result(left); + result += right; + + // only keep the right reference date if the left argument does not have one + if (!left.hasValidReference() && right.hasValidReference()) { + result.setReferenceDate(right.referenceDate()); + } + + return result; +} + +/*! + Substracts another QTimeSpan from this QTimeSpan. + + The value of the interval of the right QTimeSpan is substracted from the + left QTimeSpan with normal arithmatic. Negative values will work as expected. + + If the left argument has a reference date, that reference will be kept. + If only the right argument has a reference date, then that reference + date will be used as the new reference date. + + \sa operator+ +*/ +QTimeSpan operator-(const QTimeSpan &left, const QTimeSpan &right) +{ + QTimeSpan result(left); + result -= right; + + // only keep the right reference date if the left argument does not have one + if (!left.hasValidReference() && right.hasValidReference()) { + result.setReferenceDate(right.referenceDate()); + } + + return result; + +} + +/*! + Multiply a QTimeSpan by a scalar factor. + + \returns a new QTimeSpan object that has the same reference date as the + left QTimeSpan, but with an interval length that is multiplied by the + right argument. +*/ +QTimeSpan operator*(const QTimeSpan &left, qreal right) +{ + QTimeSpan result(left); + result*=right; + return result; +} + +/*! + Multiply a QTimeSpan by a scalar factor. + + \returns a new QTimeSpan object that has the same reference date as the + left QTimeSpan, but with an interval length that is multiplied by the + right argument. +*/ +QTimeSpan operator*(const QTimeSpan &left, int right) +{ + QTimeSpan result(left); + result*=right; + return result; +} + +/*! + Devide a QTimeSpan by a scalar factor. + + \returns a new QTimeSpan object that has the same reference date as the + left QTimeSpan, but with an interval length that is devided by the + right argument. +*/ +QTimeSpan operator/(const QTimeSpan &left, qreal right) +{ + QTimeSpan result(left); + result/=right; + return result; +} + +/*! + Devide a QTimeSpan by a scalar factor. + + \returns a new QTimeSpan object that has the same reference date as the + left QTimeSpan, but with an interval length that is devided by the + right argument. +*/ +QTimeSpan operator/(const QTimeSpan &left, int right) +{ + QTimeSpan result(left); + result/=right; + return result; +} + +/*! + Devides two QTimeSpans. The devision works on the interval lengths of + the two QTimeSpan objects as you would expect from normal artithmatic. +*/ +qreal operator/(const QTimeSpan &left, const QTimeSpan &right) +{ + return (qreal(left.toMSecs()) / qreal(right.toMSecs())); +} + +/*! + Returns a QTimeSpan object with the same reference date as the right + hand argument, but with a negated interval. Note that unlike with the + normalize() method, this function will result in a QTimeSpan that + describes a different period if the QTimeSpan has a reference date because + the reference date is not modified. +*/ +QTimeSpan operator-(const QTimeSpan &right) // Unary negation +{ + QTimeSpan result(right); + result.setFromMSecs(-result.toMSecs()); + return result; +} + +/*! + \returns the union of the two QTimeSpans. + + \sa united +*/ +QTimeSpan operator|(const QTimeSpan &left, const QTimeSpan &right) // Union +{ + QTimeSpan result(left); + result|=right; + return result; +} + +/*! + \returns the intersection of the two QTimeSpans. + + \sa intersected +*/ +QTimeSpan operator&(const QTimeSpan &left, const QTimeSpan &right) // Intersection +{ + QTimeSpan result(left); + result&=right; + return result; +} + +// Operators that use QTimeSpan and other date/time classes +/*! + Creates a new QTimeSpan object that describes the period between the two + QDateTime objects. The right hand object will be used as the reference date, + so that substracting a date in the past from a date representing now will yield + a positive QTimeSpan. + + \note that while substracting two dates will result in a QTimeSpan describing + the time between those dates, there is no pendant operation for adding two dates. + + Substractions involving an invalid QDateTime, will result in a time span with + an interval length 0. If the right-hand QDateTime is valid, it will still be + used as the reference date. +*/ +QTimeSpan operator-(const QDateTime &left, const QDateTime &right) +{ + QTimeSpan result(right); +#if QT_VERSION >= 0x040700 + if (left.isValid() && right.isValid()) { + result = QTimeSpan(right, right.msecsTo(left)); + } +#else + if (left.isValid() && right.isValid()) { + QDateTime tmpLeft = left.toUTC(); + QDateTime tmpRight = right.toUTC(); + //don't use QDateTime::secsTo, you end up in rounding issues as well as overflow problems! + qint64 interval = 1000LL * 60LL * 60LL * 24LL * qint64(tmpRight.date().daysTo(tmpLeft.date())); + interval += tmpRight.time().msecsTo(tmpLeft.time()); + result.setFromMSecs(interval); + } +#endif + return result; +} + +/*! + Creates a new QTimeSpan object that describes the period between the two + QDate objects. The right hand object will be used as the reference date, + so that substracting a date in the past from a date representing now will yield + a positive QTimeSpan. + + \note that while substracting two dates will result in a QTimeSpan describing + the time between those dates, there is no pendant operation for adding two dates. +*/ +QTimeSpan operator-(const QDate &left, const QDate &right) +{ + QTimeSpan result = QDateTime(left) - QDateTime(right); + return result; +} + +/*! + Creates a new QTimeSpan object that describes the period between the two + QTime objects. The right hand time will be used as the reference time, + so that substracting a time in the past from a time representing now will yield + a positive QTimeSpan. + + \note that that both times will be assumed to be on the current date. + + \note that while substracting two times will result in a QTimeSpan describing + the time between those, there is no pendant operation for adding two times. +*/ +QTimeSpan operator-(const QTime &left, const QTime &right) +{ + return QDateTime(QDate::currentDate(), left) - QDateTime(QDate::currentDate(), right); +} + +/*! + \returns the date described by the left-hand date, shifted by the interval + described in the QTimeSpan. The reference date of the QTimeSpan, if set, is + ignored. + + No rounding takes place. If a QTimeSpan describes 1 day, 23 hours and 59 minutes, + adding that QTimeSpan to a QDate respresenting April 1 will still yield April 2. +*/ +QDate operator+(const QDate &left, const QTimeSpan &right) +{ + QDateTime dt(left); + return (dt + right).date(); +} + +/*! + \returns the date and time described by the left-hand QDateTime, shifted by + the interval described in the QTimeSpan. The reference date of the QTimeSpan, if set, + is ignored. +*/ +QDateTime operator+(const QDateTime &left, const QTimeSpan &right) +{ + QDateTime result(left); + result = result.addMSecs(right.toMSecs()); + return result; +} + +/*! + \returns the time described by the left-hand QTime, shifted by + the interval described in the QTimeSpan. The reference date of the QTimeSpan, if set, + is ignored. + + \note that since QTimeSpan works with dates and times, the time returned will never + be bigger than 23:59:59.999. The time will wrap to the next date. Use QDateTime objects + if you need to keep track of that. +*/ +QTime operator+(const QTime &left, const QTimeSpan &right) +{ + QDateTime dt(QDate::currentDate(), left); + dt = dt.addMSecs(right.toMSecs()); + return dt.time(); +} + +/*! + \returns the date described by the left-hand date, shifted by the negated interval + described in the QTimeSpan. The reference date of the QTimeSpan, if set, is + ignored. + + No rounding takes place. If a QTimeSpan describes 1 day, 23 hours and 59 minutes, + adding that QTimeSpan to a QDate respresenting April 1 will still yield April 2. +*/ +QDate operator-(const QDate &left, const QTimeSpan &right) +{ + QDateTime dt(left); + return (dt - right).date(); +} + +/*! + \returns the date and time described by the left-hand QDateTime, shifted by + the negated interval described in the QTimeSpan. The reference date of the + QTimeSpan, if set, is ignored. +*/ +QDateTime operator-(const QDateTime &left, const QTimeSpan &right) +{ + QDateTime result(left); + result = result.addMSecs( -(right.toMSecs()) ); + return result; +} + +/*! + \returns the time described by the left-hand QTime, shifted by + the negated interval described in the QTimeSpan. The reference date of + the QTimeSpan, if set, is ignored. + + \note that since QTimeSpan works with dates and times, the time returned will never + be bigger than 23:59:59.999. The time will wrap to the next date. Use QDateTimes + if you need to keep track of that. +*/ +QTime operator-(const QTime &left, const QTimeSpan &right) +{ + QDateTime dt(QDate::currentDate(), left); + dt = dt.addMSecs( -(right.toMSecs()) ); + return dt.time(); +} + +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_NO_DATESTRING) +/*! + Operator to stream QTimeSpan objects to a debug stream. +*/ +QDebug operator<<(QDebug debug, const QTimeSpan &ts) +{ + debug << "QTimeSpan(Reference Date =" << ts.referenceDate() + << "msecs =" << ts.toMSecs() << ")"; + return debug; +} +#endif + +//String conversions +#ifndef QT_NO_DATESTRING +/*! + \returns an approximate representation of the time span length + + When representing the lenght of a time span, it is often not nessecairy to be + completely accurate. For instance, when dispaying the age of a person, it is + often enough to just state the number of years, or possibly the number of years + and the number of months. Similary, when displaying how long a certain operation + the user of your application started will run, it is useless to display the + number of seconds left if the operation will run for hours more. + + toApproximateString() provides functionality to display the length of the + QTimeSpan in such an approximate way. It will format the time using one or two + neighbouring time units. The first time unit that will be used is the unit + that represents the biggest portion of time in the span. The second time unit + will be the time unit directly under that. The second unit will only be used + if it is not 0, and if the first number is smaller than the indicated + suppresSecondUnitLimit. + + The suppressSecondUnitLimit argument can be used to suppres, for instance, + the number of seconds when the operation will run for more than five minutes + more. The idea is that for an approximate representation of the time length, + it is no longer relevant to display the second unit if the first respresents + a time span that is perhaps an order of magnitude larger already. + + If you set suppressSecondUnitLimit to a negative number, the second unit will + always be displayed, unless no valid unit for it could be found. +*/ +QString QTimeSpan::toApproximateString(int suppresSecondUnitLimit, Qt::TimeSpanFormat format) +{ + if (format==Qt::NoUnit) + return QString(); + + //retreive the time unit to use as the primairy unit + int primairy = -1; + int secondairy = -1; + + Qt::TimeSpanUnit primairyUnit = magnitude(); + while (!format.testFlag(primairyUnit ) && primairyUnit > Qt::NoUnit) { + primairyUnit = Qt::TimeSpanUnit(primairyUnit / 2); + } + + Qt::TimeSpanUnit secondairyUnit = Qt::NoUnit; + if (primairyUnit > 1) { + secondairyUnit = Qt::TimeSpanUnit(primairyUnit / 2); + } else { + primairy = 0; + } + while (!format.testFlag(secondairyUnit) && secondairyUnit > Qt::NoUnit) { + secondairyUnit = Qt::TimeSpanUnit(secondairyUnit / 2); + } + + //build up hash with pointers to ints for the units that are set in format, and 0's for those that are not. + if (primairy < 0) { + QTimeSpanPrivate::TimePartHash partsHash(format); + bool result = partsHash.fill(*this); + + if (!result) { + qDebug() << "false result from parts function"; + return QString(); + } + + primairy = *(partsHash.value(primairyUnit)); + if (secondairyUnit > 0) { + secondairy = *(partsHash.value(secondairyUnit)); + } else { + secondairy = 0; + } + } + + if ((primairy > 0 + && secondairy > 0 + && primairy < suppresSecondUnitLimit) + || (suppresSecondUnitLimit < 0 + && secondairyUnit > Qt::NoUnit) ) + { + //we will display with two units + return d->unitString(primairyUnit, primairy) + QLatin1String(", ") + d->unitString(secondairyUnit, secondairy); + } + + //we will display with only the primairy unit + return d->unitString(primairyUnit, primairy); +} + +/*! + \returns a string representation of the duration of this time span in the requested format + + This function returns a representation of only the length of this time span. If + you need the reference or referenced dates, access those using one of the provided + methods and output them directly. + + The format parameter determines the format of the result string. The duration will be + expressed in the units you use in the format. + + y The number of years + M The number of months + w The number of weeks + d The number of days + h The number of hours + m The number of minutes + s The number of seconds + z The number of milliseconds + + Use multiple letters to force leading zeros. + + \note You can not use years or months if the time span does not have a valid reference + date. + + Characters in the string that don't represent a time unit, are used as literal strings in the + output. Everything between single quotes will always be used as a literal string. This makes + it possible to use the characters used for the time span format also as literal output. To use a + single quote in the output, put two consecutive single quotes within a single quote literal + string block. To just put a single quote in a the output, you need four consequtive single + quotes. +*/ +QString QTimeSpan::toString(const QString &format) const +{ + Qt::TimeSpanFormat tsFormat = Qt::NoUnit; + QList tokenList = d->parseFormatString(format, tsFormat); + + QTimeSpanPrivate::TimePartHash partsHash(tsFormat); + bool result = partsHash.fill(*this); + + if (!result) + return QString(); + + QString formattedString; + foreach(QTimeSpanPrivate::TimeFormatToken token, tokenList) { + if (token.type == 0) { + formattedString.append(token.string); + } else { + Qt::TimeSpanUnit unit(token.type); + formattedString.append (QString("%1").arg(*partsHash.value( unit ), + token.length, + 10, + QChar('0') ) ); + } + } + + return formattedString; +} + +/*! + Returns a time span represented by the string using the format given, or an empty + time span if the string cannot be parsed. + + The optional reference argument will be used as the reference date for the string. + + \note You can only use months or years if you also pass a valid reference. + */ +QTimeSpan QTimeSpan::fromString(const QString &string, const QString &format, const QDateTime &reference) +{ + /* + + There are two possible ways of parsing a string. On the one hand, you could use the + lengths of string literals to determine the positions in the string where you expect + the different parts of the string. On the other hand, you could use the actual contents + of the literals as delimiters to figure out what parts of the string refer to what + unit of time. In that case, the length of the time units would only matter if they are + not surrounded by a string literal. Both seem useful. Perhaps we need two different + modes for this? + + The code here implements the first option. The overloaded version below implements a + more flexible regexp based approach. + */ + + //stage one: parse the format string + QTimeSpan span(reference); + Qt::TimeSpanFormat tsFormat = Qt::NoUnit; + QList tokenList = span.d->parseFormatString(format, tsFormat); + + //prepare the temporaries + QTimeSpanPrivate::TimePartHash partsHash(tsFormat); + QString input(string); + + //extract the values from the input string into our temporary structure + foreach(const QTimeSpanPrivate::TimeFormatToken token, tokenList) { + if (token.type == Qt::NoUnit) { + input = input.remove(0, token.length); + } else { + QString part = input.left(token.length); + input = input.remove(0, token.length); + + bool success(false); + part = part.trimmed(); + int value = part.toInt(&success, 10); + if (!success) { + return QTimeSpan(); + } + *(partsHash.value(token.type)) = value; + } + } + + //construct the time span from the temporary data + //we must set the number of years and months first; for the rest order is not important + if (partsHash.value(Qt::Years)) { + span.d->addUnit(&span, Qt::Years, *(partsHash.value(Qt::Years))); + delete partsHash.value(Qt::Years); + partsHash.insert(Qt::Years, 0); + } + if (partsHash.value(Qt::Months)) { + span.d->addUnit(&span, Qt::Months, *(partsHash.value(Qt::Months))); + delete partsHash.value(Qt::Months); + partsHash.insert(Qt::Months, 0); + } + + //add the rest of the units + QHashIterator it(partsHash); + while (it.hasNext()) { + it.next(); + if (it.value()) { + span.d->addUnit(&span, it.key(), *(it.value())); + qDebug() << "Added unit" << it.key() << "with value" << *(it.value()) << "new value" << span.d->interval; + } + } + + return span; +} + +/*! + Returns a time span represented by the string using the patern given, or an empty + time span if the string cannot be parsed. Each pair of capturing parenthesis can + extract a time unit. The order in which the units appear is given by the list of + arguments unit1 to unit8. Captures for which the corresponding type is set to + Qt::NoUnit will be ignored. + + The reference argument will be used as the reference date for the string. + + \note You can only use months or years if you also pass a valid reference. + */ +QTimeSpan QTimeSpan::fromString(const QString &string, const QRegExp &pattern, const QDateTime &reference, + Qt::TimeSpanUnit unit1, Qt::TimeSpanUnit unit2, Qt::TimeSpanUnit unit3, + Qt::TimeSpanUnit unit4, Qt::TimeSpanUnit unit5, Qt::TimeSpanUnit unit6, + Qt::TimeSpanUnit unit7, Qt::TimeSpanUnit unit8) +{ + if (pattern.indexIn(string) < 0) + return QTimeSpan(); + + QTimeSpanPrivate::TimePartHash partsHash(Qt::NoUnit); + + QList unitList; + unitList << unit1 << unit2 << unit3 << unit4 << unit5 << unit6 << unit7 << unit8; + + for (int i(0); i < qMin(pattern.captureCount(), 8 ); ++i) { + if (unitList.at(i) > Qt::NoUnit) { + partsHash.addUnit(unitList.at(i)); + QString capture = pattern.cap(i + 1); + bool ok(false); + int value = capture.toInt(&ok, 10); + if (!ok) + return QTimeSpan(); + + *(partsHash.value(unitList.at(i))) = value; + } + } + + //create the time span to return + QTimeSpan span(reference); + + //construct the time span from the temporary data + //we must set the number of years and months first; for the rest order is not important + if (partsHash.value(Qt::Years)) { + span.d->addUnit(&span, Qt::Years, *(partsHash.value(Qt::Years))); + delete partsHash.value(Qt::Years); + partsHash.insert(Qt::Years, 0); + } + if (partsHash.value(Qt::Months)) { + span.d->addUnit(&span, Qt::Months, *(partsHash.value(Qt::Months))); + delete partsHash.value(Qt::Months); + partsHash.insert(Qt::Months, 0); + } + + //add the rest of the units + QHashIterator it(partsHash); + while (it.hasNext()) { + it.next(); + if (it.value()) { + span.d->addUnit(&span, it.key(), *(it.value())); + } + } + + return span; +} +#endif + +QT_END_NAMESPACE diff --git a/libraries/shared/src/qtimespan.h b/libraries/shared/src/qtimespan.h new file mode 100644 index 0000000000..03ed69abe3 --- /dev/null +++ b/libraries/shared/src/qtimespan.h @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTIMESPAN_H +#define QTIMESPAN_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Core) + +//Move this to qnamespace.h when integrating +namespace Qt +{ + enum TimeSpanUnit { + Milliseconds = 0x0001, + Seconds = 0x0002, + Minutes = 0x0004, + Hours = 0x0008, + Days = 0x0010, + Weeks = 0x0020, + Months = 0x0040, + Years = 0x0080, + DaysAndTime = Days | Hours | Minutes | Seconds, + AllUnits = Milliseconds | DaysAndTime | Months | Years, + NoUnit = 0 + }; + + Q_DECLARE_FLAGS(TimeSpanFormat, TimeSpanUnit) +} +Q_DECLARE_OPERATORS_FOR_FLAGS(Qt::TimeSpanFormat) + +//end of section to move + +class QTimeSpanPrivate; + +class Q_CORE_EXPORT QTimeSpan +{ +public: + QTimeSpan(); + explicit QTimeSpan(qint64 msecs); + explicit QTimeSpan(const QDateTime& reference, qint64 msecs = 0); + explicit QTimeSpan(const QDate& reference, quint64 msecs = 0); + explicit QTimeSpan(const QTime& reference, quint64 msecs = 0); + explicit QTimeSpan(const QDateTime& reference, const QTimeSpan& other); + explicit QTimeSpan(const QDate& reference, const QTimeSpan& other); + explicit QTimeSpan(const QTime& reference, const QTimeSpan& other); + QTimeSpan(const QTimeSpan& other); + + + ~QTimeSpan(); + + // constant time units + static const QTimeSpan Second; + static const QTimeSpan Minute; + static const QTimeSpan Hour; + static const QTimeSpan Day; + static const QTimeSpan Week; + + // status/validity of the time span + bool isEmpty() const; + bool isNull() const; + + // This set of functions operates on a single component of the time span. + inline int msecsPart(Qt::TimeSpanFormat format = Qt::DaysAndTime | Qt::Milliseconds) const {return part(Qt::Milliseconds, format);} + inline int secsPart(Qt::TimeSpanFormat format = Qt::DaysAndTime) const {return part(Qt::Seconds, format);} + inline int minutesPart(Qt::TimeSpanFormat format = Qt::DaysAndTime) const {return part(Qt::Minutes, format);} + inline int hoursPart(Qt::TimeSpanFormat format = Qt::DaysAndTime) const {return part(Qt::Hours, format);} + inline int daysPart(Qt::TimeSpanFormat format = Qt::DaysAndTime) const {return part(Qt::Days, format);} + inline int weeksPart(Qt::TimeSpanFormat format = Qt::DaysAndTime) const {return part(Qt::Weeks, format);} + //int monthsPart(Qt::TimeSpanFormat format) const; + //int yearsPart(Qt::TimeSpanFormat format) const; + int part(Qt::TimeSpanUnit unit, Qt::TimeSpanFormat format = Qt::DaysAndTime) const; + + bool parts(int *msecondsPtr, + int *secondsPtr = 0, + int *minutesPtr = 0, + int *hoursPtr = 0, + int *daysPtr = 0, + int *weeksPtr = 0, + int *monthsPtr = 0, + int *yearsPtr = 0, + qreal *fractionalSmallestUnit = 0) const; + + Qt::TimeSpanUnit magnitude(); + + inline void setMSecsPart(int msecs, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Milliseconds, msecs, format);} + inline void setSecsPart(int seconds, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Seconds, seconds, format);} + inline void setMinutesPart(int minutes, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Minutes, minutes, format);} + inline void setHoursPart(int hours, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Hours, hours, format);} + inline void setDaysPart(int days, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Days, days, format);} + inline void setWeeksPart(int weeks, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Weeks, weeks, format);} + inline void setMonthsPart(int months, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Months, months, format);} + inline void setYearsPart(int years, Qt::TimeSpanFormat format = Qt::DaysAndTime) {setPart(Qt::Years, years, format);} + void setPart(Qt::TimeSpanUnit unit, int interval, Qt::TimeSpanFormat format = Qt::DaysAndTime); + + // This set of functions operator on the entire timespan and not + // just a single component of it. + qint64 toMSecs() const; + inline qreal toSecs() const {return toTimeUnit(Qt::Seconds);} + inline qreal toMinutes() const {return toTimeUnit(Qt::Minutes);} + inline qreal toHours() const {return toTimeUnit(Qt::Hours);} + inline qreal toDays() const {return toTimeUnit(Qt::Days);} + inline qreal toWeeks() const {return toTimeUnit(Qt::Weeks);} + inline qreal toMonths() const {return toTimeUnit(Qt::Seconds);} + inline qreal toYears() const {return toTimeUnit(Qt::Seconds);} + qreal toTimeUnit(Qt::TimeSpanUnit unit) const; + + void setFromMSecs(qint64 msecs); + inline void setFromSecs(qreal secs) {setFromTimeUnit(Qt::Seconds, secs);} + inline void setFromMinutes(qreal minutes) {setFromTimeUnit(Qt::Minutes, minutes);} + inline void setFromHours(qreal hours) {setFromTimeUnit(Qt::Hours, hours);} + inline void setFromDays(qreal days) {setFromTimeUnit(Qt::Days, days);} + inline void setFromWeeks(qreal weeks) {setFromTimeUnit(Qt::Weeks, weeks);} + void setFromMonths(qreal months); + void setFromYears(qreal years); + void setFromTimeUnit(Qt::TimeSpanUnit unit, qreal interval); + + // Reference date + bool hasValidReference() const; + QDateTime referenceDate() const; + void setReferenceDate(const QDateTime &referenceDate); + void moveReferenceDate(const QDateTime &referenceDate); + void setReferencedDate(const QDateTime &referencedDate); + void moveReferencedDate(const QDateTime &referencedDate); + + // Referenced date - referenceDate() + *this + QDateTime referencedDate() const; + + // Pretty printing +#ifndef QT_NO_DATESTRING + QString toString(const QString &format) const; + QString toApproximateString(int suppresSecondUnitLimit = 3, + Qt::TimeSpanFormat format = Qt::Seconds | Qt::Minutes | Qt::Hours | Qt::Days | Qt::Weeks); +#endif + + // Assignment operator + QTimeSpan &operator=(const QTimeSpan& other); + + // Comparison operators + bool operator==(const QTimeSpan &other) const; + inline bool operator!=(const QTimeSpan &other) const {return !(operator==(other));} + bool operator<(const QTimeSpan &other) const; + bool operator<=(const QTimeSpan &other) const; + inline bool operator>(const QTimeSpan &other) const {return !(operator<=(other));} + inline bool operator>=(const QTimeSpan &other) const {return !(operator<(other));} + bool matchesLength(const QTimeSpan &other, bool normalize = false) const; + + // Arithmetic operators. Operators that don't change *this are declared as non-members. + QTimeSpan &operator+=(const QTimeSpan &other); + QTimeSpan &operator+=(qint64 msecs); + QTimeSpan &operator-=(const QTimeSpan &other); + QTimeSpan &operator-=(qint64 msecs); + QTimeSpan &operator*=(qreal factor); + QTimeSpan &operator*=(int factor); + QTimeSpan &operator/=(qreal factor); + QTimeSpan &operator/=(int factor); + QTimeSpan &operator|=(const QTimeSpan &other); // Union + QTimeSpan &operator&=(const QTimeSpan &other); // Intersection + + // Ensure the reference date is before the referenced date + QTimeSpan normalized() const; + void normalize(); + QTimeSpan abs() const; + bool isNegative() const; + bool isNormal() const {return !isNegative();} + + // Naturally ordered dates + QDateTime startDate() const; + QDateTime endDate() const; + + // Containment + bool contains(const QDateTime &dateTime) const; + bool contains(const QDate &date) const; + bool contains(const QTime &time) const; + bool contains(const QTimeSpan &other) const; + + bool overlaps(const QTimeSpan &other) const; + QTimeSpan overlapped(const QTimeSpan &other) const; + QTimeSpan united(const QTimeSpan &other) const; + + // Static construction methods +#ifndef QT_NO_DATESTRING + static QTimeSpan fromString(const QString &string, const QString &format, const QDateTime& reference = QDateTime()); + static QTimeSpan fromString(const QString &string, const QRegExp &pattern, const QDateTime& reference, + Qt::TimeSpanUnit unit1, + Qt::TimeSpanUnit unit2 = Qt::NoUnit, Qt::TimeSpanUnit unit3 = Qt::NoUnit, + Qt::TimeSpanUnit unit4 = Qt::NoUnit, Qt::TimeSpanUnit unit5 = Qt::NoUnit, + Qt::TimeSpanUnit unit6 = Qt::NoUnit, Qt::TimeSpanUnit unit7 = Qt::NoUnit, + Qt::TimeSpanUnit unit8 = Qt::NoUnit); +#endif + //static QTimeSpan fromString(const QString &string, Qt::TimeSpanFormat format); + static QTimeSpan fromTimeUnit(Qt::TimeSpanUnit unit, qreal interval, const QDateTime& reference = QDateTime()); + /* + static QTimeSpan fromMSecs(qint64 msecs); + static QTimeSpan fromSecs(qreal secs) {return QTimeSpan::Second * secs;} + static QTimeSpan fromMinutes(qreal minutes) {return QTimeSpan::Minute * minutes;} + static QTimeSpan fromHours(qreal hours) {return QTimeSpan::Hour * hours;} + static QTimeSpan fromDays(qreal days) {return QTimeSpan::Day * days;} + static QTimeSpan fromWeeks(qreal weeks) {return QTimeSpan::Week * weeks;} + */ + +private: +#ifndef QT_NO_DATASTREAM + friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QTimeSpan &); + friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QTimeSpan &); +#endif + + QSharedDataPointer d; +}; +Q_DECLARE_TYPEINFO(QTimeSpan, Q_MOVABLE_TYPE); +Q_DECLARE_METATYPE(QTimeSpan); +Q_DECLARE_METATYPE(Qt::TimeSpanUnit); + +//non-member operators +Q_CORE_EXPORT QTimeSpan operator+(const QTimeSpan &left, const QTimeSpan &right); +Q_CORE_EXPORT QTimeSpan operator-(const QTimeSpan &left, const QTimeSpan &right); +Q_CORE_EXPORT QTimeSpan operator*(const QTimeSpan &left, qreal right);//no problem +Q_CORE_EXPORT QTimeSpan operator*(const QTimeSpan &left, int right);//no problem +inline QTimeSpan operator*(qreal left, const QTimeSpan &right) {return right * left;} // works +inline QTimeSpan operator*(int left, const QTimeSpan &right) {return right * left;} // works +//Q_CORE_EXPORT QTimeSpan operator*(qreal left, const QTimeSpan &right) {return right * left;} //does not work +//Q_CORE_EXPORT QTimeSpan operator*(int left, const QTimeSpan &right) {return right * left;} //does not work +Q_CORE_EXPORT QTimeSpan operator/(const QTimeSpan &left, qreal right); +Q_CORE_EXPORT QTimeSpan operator/(const QTimeSpan &left, int right); +Q_CORE_EXPORT qreal operator/(const QTimeSpan &left, const QTimeSpan &right); +Q_CORE_EXPORT QTimeSpan operator-(const QTimeSpan &right); // Unary negation +Q_CORE_EXPORT QTimeSpan operator|(const QTimeSpan &left, const QTimeSpan &right); // Union +Q_CORE_EXPORT QTimeSpan operator&(const QTimeSpan &left, const QTimeSpan &right); // Intersection + +// Operators that use QTimeSpan and other date/time classes +Q_CORE_EXPORT QTimeSpan operator-(const QDateTime &left, const QDateTime &right); +Q_CORE_EXPORT QTimeSpan operator-(const QDate &left, const QDate &right); +Q_CORE_EXPORT QTimeSpan operator-(const QTime &left, const QTime &right); +Q_CORE_EXPORT QDate operator+(const QDate &left, const QTimeSpan &right); +Q_CORE_EXPORT QDate operator-(const QDate &left, const QTimeSpan &right); +Q_CORE_EXPORT QTime operator+(const QTime &left, const QTimeSpan &right); +Q_CORE_EXPORT QTime operator-(const QTime &left, const QTimeSpan &right); +Q_CORE_EXPORT QDateTime operator+(const QDateTime &left, const QTimeSpan &right); +Q_CORE_EXPORT QDateTime operator-(const QDateTime &left, const QTimeSpan &right); + + +#ifndef QT_NO_DATASTREAM +Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QTimeSpan &); +Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QTimeSpan &); +#endif // QT_NO_DATASTREAM + +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_NO_DATESTRING) +Q_CORE_EXPORT QDebug operator<<(QDebug, const QTimeSpan &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTIMESPAN_H From 86643803cf0456996ed37114185dc5ca335c285d Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Mon, 10 Mar 2014 16:01:53 +0200 Subject: [PATCH 05/13] Moved the connection and configuration of XMPP to the log-in process. --- CMakeLists.txt | 6 --- cmake/modules/FindQxmpp.cmake | 4 +- interface/CMakeLists.txt | 1 + interface/interface_en.ts | 16 +++---- interface/src/Menu.cpp | 15 ++++++- interface/src/Menu.h | 2 + interface/src/XmppClient.cpp | 65 ++++++++++++++++++++++++++++ interface/src/XmppClient.h | 43 +++++++++++++++++++ interface/src/ui/ChatWindow.cpp | 74 ++++++++++++++++++-------------- interface/src/ui/ChatWindow.h | 6 +-- interface/src/ui/FlowLayout.cpp | 2 +- libraries/shared/CMakeLists.txt | 2 +- libraries/shared/src/qtimespan.h | 54 +++++++++++------------ 13 files changed, 208 insertions(+), 82 deletions(-) create mode 100644 interface/src/XmppClient.cpp create mode 100644 interface/src/XmppClient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b3e1af23e8..33589ddb57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,12 +34,6 @@ if (APPLE) endif (DARWIN_VERSION GREATER 12) endif (APPLE) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") - -find_package(qxmpp REQUIRED) -add_definitions(-DQXMPP_STATIC) -include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) - # targets not supported on windows if (NOT WIN32) add_subdirectory(animation-server) diff --git a/cmake/modules/FindQxmpp.cmake b/cmake/modules/FindQxmpp.cmake index f50212d63f..13ff1ed855 100644 --- a/cmake/modules/FindQxmpp.cmake +++ b/cmake/modules/FindQxmpp.cmake @@ -29,10 +29,10 @@ else () if (QXMPP_FOUND) if (NOT QXMPP_FIND_QUIETLY) message(STATUS "Found qxmpp: ${QXMPP_LIBRARY}") - endif (NOT QXMPP_FIND_QUIETLY) + endif () else () if (QXMPP_FIND_REQUIRED) message(FATAL_ERROR "Could not find qxmpp") - endif (SIXENSE_FIND_REQUIRED) + endif () endif () endif () \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 6ec0b6f679..2559c00207 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -182,6 +182,7 @@ target_link_libraries( ${TARGET_NAME} "${FACESHIFT_LIBRARIES}" "${ZLIB_LIBRARIES}" + "${QXMPP_LIBRARY}" Qt5::Core Qt5::Gui Qt5::Multimedia Qt5::Network Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKit Qt5::WebKitWidgets Qt5::Xml Qt5::UiTools ) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 8198d97a8f..e5461bc1c2 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -45,7 +45,7 @@ - + day %n day @@ -53,7 +53,7 @@ - + hour %n hour @@ -61,7 +61,7 @@ - + minute %n minute @@ -76,7 +76,7 @@ - + %1 online now: @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 60e93b0cea..2331a1bceb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include "Application.h" @@ -161,7 +162,12 @@ 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())); + + _chatAction = addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, 0, this, SLOT(showChat())); + const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); + toggleChat(); + connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat())); + connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat())); QMenu* viewMenu = addMenu("View"); @@ -1039,6 +1045,13 @@ void Menu::showChat() { _chatWindow->raise(); } +void Menu::toggleChat() { + _chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected()); + if (!_chatAction->isEnabled() && _chatWindow) { + _chatWindow->close(); + } +} + 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 cd32a9c8df..380fe2d3b7 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -144,6 +144,7 @@ private slots: void runTests(); void showMetavoxelEditor(); void showChat(); + void toggleChat(); void audioMuteToggled(); private: @@ -204,6 +205,7 @@ private: quint64 _lastAdjust; SimpleMovingAverage _fpsAverage; QAction* _loginAction; + QAction* _chatAction; }; namespace MenuOption { diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp new file mode 100644 index 0000000000..a3716c781e --- /dev/null +++ b/interface/src/XmppClient.cpp @@ -0,0 +1,65 @@ +// +// XmppClient.cpp +// interface +// +// Created by Dimitar Dobrev on 10/3/14 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include "XmppClient.h" + +const QString DEFAULT_XMPP_SERVER = "chat.highfidelity.io"; +const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; + +XmppClient::XmppClient() : + _xmppClient(), + _xmppMUCManager() { + AccountManager& accountManager = AccountManager::getInstance(); + connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); + connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); +} + +XmppClient& XmppClient::getInstance() { + static XmppClient sharedInstance; + return sharedInstance; +} + +void XmppClient::xmppConnected() { + _publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); + _publicChatRoom->setNickName(AccountManager::getInstance().getUsername()); + _publicChatRoom->join(); +} + +void XmppClient::xmppError(QXmppClient::Error error) { + qDebug() << "Error connnecting to XMPP for user " << AccountManager::getInstance().getUsername() << ": " << error; +} + +void XmppClient::connectToServer() { + disconnectFromServer(); + + if (_xmppClient.addExtension(&_xmppMUCManager)) { + connect(&_xmppClient, SIGNAL(connected()), this, SLOT(xmppConnected())); + connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error))); + } + AccountManager& accountManager = AccountManager::getInstance(); + QString user = accountManager.getUsername(); + const QString& password = accountManager.getXMPPPassword(); + _xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password); +} + +void XmppClient::disconnectFromServer() +{ + if (_xmppClient.isConnected()) { + _xmppClient.disconnectFromServer(); + } +} + +XmppClient::XmppClient(const XmppClient& other) { + Q_UNUSED(other); +} + +void XmppClient::operator =(XmppClient const& other) { + Q_UNUSED(other); +} diff --git a/interface/src/XmppClient.h b/interface/src/XmppClient.h new file mode 100644 index 0000000000..1ebe868424 --- /dev/null +++ b/interface/src/XmppClient.h @@ -0,0 +1,43 @@ +// +// XmppClient.h +// interface +// +// Created by Dimitar Dobrev on 10/3/14 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__XmppClient__ +#define __interface__XmppClient__ + +#include +#include +#include + +/// Generalized threaded processor for handling received inbound packets. +class XmppClient : public QObject { + Q_OBJECT + +public: + static XmppClient& getInstance(); + + QXmppClient& getXMPPClient() { return _xmppClient; } + const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; } + +private slots: + void xmppConnected(); + void xmppError(QXmppClient::Error error); + + void connectToServer(); + void disconnectFromServer(); + +private: + XmppClient(); + XmppClient(XmppClient const& other); // not implemented + void operator=(XmppClient const& other); // not implemented + + QXmppClient _xmppClient; + QXmppMucManager _xmppMUCManager; + QXmppMucRoom* _publicChatRoom; +}; + +#endif // __interface__XmppClient__ diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 4e4b869cda..7bcec5a2a9 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -16,17 +16,13 @@ #include #include "ChatWindow.h" -#include "ui_chatwindow.h" +#include "ui_chatWindow.h" #include "FlowLayout.h" #include "qtimespan.h" -#include -#include #include -#include +#include -const QString DEFAULT_SERVER = "chat.highfidelity.io"; -const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); @@ -42,25 +38,34 @@ ChatWindow::ChatWindow() : 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); + const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); + if (xmppClient.isConnected()) { + participantsChanged(); + const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); + connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + ui->connectingToXMPPLabel->hide(); + startTimerForTimeStamps(); + } + else { + ui->numOnlineLabel->hide(); + ui->usersWidget->hide(); + ui->messagesScrollArea->hide(); + ui->messagePlainTextEdit->hide(); + connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); + } + connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); } ChatWindow::~ChatWindow() { + const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); + disconnect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); + disconnect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); + + const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); + disconnect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + delete ui; } @@ -75,7 +80,8 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { (keyEvent->modifiers() & Qt::ShiftModifier) == 0) { QString message = ui->messagePlainTextEdit->document()->toPlainText(); if (!message.trimmed().isEmpty()) { - _xmppClient.sendMessage(_chatRoom->jid(), message); + const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); + XmppClient::getInstance().getXMPPClient().sendMessage(publicChatRoom->jid(), message); ui->messagePlainTextEdit->document()->clear(); } return true; @@ -84,7 +90,8 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { } QString ChatWindow::getParticipantName(const QString& participant) { - return participant.right(participant.count() - 1 - _chatRoom->jid().count()); + const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); + return participant.right(participant.count() - 1 - publicChatRoom->jid().count()); } void ChatWindow::addTimeStamp() { @@ -107,22 +114,25 @@ void ChatWindow::addTimeStamp() { numMessagesAfterLastTimeStamp = 0; } -void ChatWindow::connected() { - _chatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); - connect(_chatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); - _chatRoom->setNickName(AccountManager::getInstance().getUsername()); - _chatRoom->join(); +void ChatWindow::startTimerForTimeStamps() +{ + QTimer* timer = new QTimer(this); + timer->setInterval(10 * 60 * 1000); + connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); + timer->start(); +} +void ChatWindow::connected() { ui->connectingToXMPPLabel->hide(); ui->numOnlineLabel->show(); ui->usersWidget->show(); ui->messagesScrollArea->show(); ui->messagePlainTextEdit->show(); - QTimer* timer = new QTimer(this); - timer->setInterval(10 * 60 * 1000); - connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); - timer->start(); + const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); + connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + + startTimerForTimeStamps(); } void ChatWindow::timeout() { @@ -136,7 +146,7 @@ void ChatWindow::error(QXmppClient::Error error) { } void ChatWindow::participantsChanged() { - QStringList participants = _chatRoom->participants(); + QStringList participants = XmppClient::getInstance().getPublicChatRoom()->participants(); ui->numOnlineLabel->setText(tr("%1 online now:").arg(participants.count())); while (QLayoutItem* item = ui->usersWidget->layout()->takeAt(0)) { diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index afa1947268..16f8928d7e 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -16,7 +16,7 @@ #include #include -#include +#include namespace Ui { class ChatWindow; @@ -34,12 +34,10 @@ protected: private: QString getParticipantName(const QString& participant); + void startTimerForTimeStamps(); void addTimeStamp(); Ui::ChatWindow* ui; - QXmppClient _xmppClient; - QXmppMucManager _xmppMUCManager; - QXmppMucRoom* _chatRoom; int numMessagesAfterLastTimeStamp; QDateTime lastMessageStamp; diff --git a/interface/src/ui/FlowLayout.cpp b/interface/src/ui/FlowLayout.cpp index 5387d9499d..c24ad6ce01 100644 --- a/interface/src/ui/FlowLayout.cpp +++ b/interface/src/ui/FlowLayout.cpp @@ -40,7 +40,7 @@ #include -#include "flowlayout.h" +#include "FlowLayout.h" //! [1] FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 40473c924a..be18a3ba45 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -32,4 +32,4 @@ if (UNIX AND NOT APPLE) target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") endif (UNIX AND NOT APPLE) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Xml "${QXMPP_LIBRARY}") +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Xml) diff --git a/libraries/shared/src/qtimespan.h b/libraries/shared/src/qtimespan.h index 03ed69abe3..83f9cdbec7 100644 --- a/libraries/shared/src/qtimespan.h +++ b/libraries/shared/src/qtimespan.h @@ -79,7 +79,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(Qt::TimeSpanFormat) class QTimeSpanPrivate; -class Q_CORE_EXPORT QTimeSpan +class QTimeSpan { public: QTimeSpan(); @@ -247,8 +247,8 @@ public: private: #ifndef QT_NO_DATASTREAM - friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QTimeSpan &); - friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QTimeSpan &); + friend QDataStream &operator<<(QDataStream &, const QTimeSpan &); + friend QDataStream &operator>>(QDataStream &, QTimeSpan &); #endif QSharedDataPointer d; @@ -258,40 +258,40 @@ Q_DECLARE_METATYPE(QTimeSpan); Q_DECLARE_METATYPE(Qt::TimeSpanUnit); //non-member operators -Q_CORE_EXPORT QTimeSpan operator+(const QTimeSpan &left, const QTimeSpan &right); -Q_CORE_EXPORT QTimeSpan operator-(const QTimeSpan &left, const QTimeSpan &right); -Q_CORE_EXPORT QTimeSpan operator*(const QTimeSpan &left, qreal right);//no problem -Q_CORE_EXPORT QTimeSpan operator*(const QTimeSpan &left, int right);//no problem +QTimeSpan operator+(const QTimeSpan &left, const QTimeSpan &right); +QTimeSpan operator-(const QTimeSpan &left, const QTimeSpan &right); +QTimeSpan operator*(const QTimeSpan &left, qreal right);//no problem +QTimeSpan operator*(const QTimeSpan &left, int right);//no problem inline QTimeSpan operator*(qreal left, const QTimeSpan &right) {return right * left;} // works inline QTimeSpan operator*(int left, const QTimeSpan &right) {return right * left;} // works -//Q_CORE_EXPORT QTimeSpan operator*(qreal left, const QTimeSpan &right) {return right * left;} //does not work -//Q_CORE_EXPORT QTimeSpan operator*(int left, const QTimeSpan &right) {return right * left;} //does not work -Q_CORE_EXPORT QTimeSpan operator/(const QTimeSpan &left, qreal right); -Q_CORE_EXPORT QTimeSpan operator/(const QTimeSpan &left, int right); -Q_CORE_EXPORT qreal operator/(const QTimeSpan &left, const QTimeSpan &right); -Q_CORE_EXPORT QTimeSpan operator-(const QTimeSpan &right); // Unary negation -Q_CORE_EXPORT QTimeSpan operator|(const QTimeSpan &left, const QTimeSpan &right); // Union -Q_CORE_EXPORT QTimeSpan operator&(const QTimeSpan &left, const QTimeSpan &right); // Intersection +//QTimeSpan operator*(qreal left, const QTimeSpan &right) {return right * left;} //does not work +//QTimeSpan operator*(int left, const QTimeSpan &right) {return right * left;} //does not work +QTimeSpan operator/(const QTimeSpan &left, qreal right); +QTimeSpan operator/(const QTimeSpan &left, int right); +qreal operator/(const QTimeSpan &left, const QTimeSpan &right); +QTimeSpan operator-(const QTimeSpan &right); // Unary negation +QTimeSpan operator|(const QTimeSpan &left, const QTimeSpan &right); // Union +QTimeSpan operator&(const QTimeSpan &left, const QTimeSpan &right); // Intersection // Operators that use QTimeSpan and other date/time classes -Q_CORE_EXPORT QTimeSpan operator-(const QDateTime &left, const QDateTime &right); -Q_CORE_EXPORT QTimeSpan operator-(const QDate &left, const QDate &right); -Q_CORE_EXPORT QTimeSpan operator-(const QTime &left, const QTime &right); -Q_CORE_EXPORT QDate operator+(const QDate &left, const QTimeSpan &right); -Q_CORE_EXPORT QDate operator-(const QDate &left, const QTimeSpan &right); -Q_CORE_EXPORT QTime operator+(const QTime &left, const QTimeSpan &right); -Q_CORE_EXPORT QTime operator-(const QTime &left, const QTimeSpan &right); -Q_CORE_EXPORT QDateTime operator+(const QDateTime &left, const QTimeSpan &right); -Q_CORE_EXPORT QDateTime operator-(const QDateTime &left, const QTimeSpan &right); +QTimeSpan operator-(const QDateTime &left, const QDateTime &right); +QTimeSpan operator-(const QDate &left, const QDate &right); +QTimeSpan operator-(const QTime &left, const QTime &right); +QDate operator+(const QDate &left, const QTimeSpan &right); +QDate operator-(const QDate &left, const QTimeSpan &right); +QTime operator+(const QTime &left, const QTimeSpan &right); +QTime operator-(const QTime &left, const QTimeSpan &right); +QDateTime operator+(const QDateTime &left, const QTimeSpan &right); +QDateTime operator-(const QDateTime &left, const QTimeSpan &right); #ifndef QT_NO_DATASTREAM -Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QTimeSpan &); -Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QTimeSpan &); +QDataStream &operator<<(QDataStream &, const QTimeSpan &); +QDataStream &operator>>(QDataStream &, QTimeSpan &); #endif // QT_NO_DATASTREAM #if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_NO_DATESTRING) -Q_CORE_EXPORT QDebug operator<<(QDebug, const QTimeSpan &); +QDebug operator<<(QDebug, const QTimeSpan &); #endif QT_END_NAMESPACE From acb279305345cea9ecd38de908a6a1bb0294fe34 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 02:05:17 +0200 Subject: [PATCH 06/13] Ensured messages are sent to the group chat rather than to individual users. --- interface/interface_en.ts | 8 ++++---- interface/src/ui/ChatWindow.cpp | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index e5461bc1c2..5aeb683c8c 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -45,7 +45,7 @@ - + day %n day @@ -53,7 +53,7 @@ - + hour %n hour @@ -61,7 +61,7 @@ - + minute %n minute @@ -76,7 +76,7 @@ - + %1 online now: diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 7bcec5a2a9..3d0c140b0d 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -78,10 +78,14 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { QKeyEvent* keyEvent = static_cast(event); if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && (keyEvent->modifiers() & Qt::ShiftModifier) == 0) { - QString message = ui->messagePlainTextEdit->document()->toPlainText(); - if (!message.trimmed().isEmpty()) { + QString messageText = ui->messagePlainTextEdit->document()->toPlainText().trimmed(); + if (!messageText.isEmpty()) { const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); - XmppClient::getInstance().getXMPPClient().sendMessage(publicChatRoom->jid(), message); + QXmppMessage message; + message.setTo(publicChatRoom->jid()); + message.setType(QXmppMessage::GroupChat); + message.setBody(messageText); + XmppClient::getInstance().getXMPPClient().sendPacket(message); ui->messagePlainTextEdit->document()->clear(); } return true; From 60bf473be7aa86be877cf80eabc4a5fad206467a Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 02:17:24 +0200 Subject: [PATCH 07/13] Improved the adherence to code conventions after a code review. --- interface/interface_en.ts | 2 +- interface/src/XmppClient.cpp | 6 +++--- interface/src/ui/ChatWindow.cpp | 20 +++++++++---------- libraries/shared/CMakeLists.txt | 2 +- .../shared/src/DataServerAccountInfo.cpp | 7 ++----- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 5aeb683c8c..fbbe4752ea 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -76,7 +76,7 @@ - + %1 online now: diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index a3716c781e..de0a47c28a 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -15,7 +15,8 @@ const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; XmppClient::XmppClient() : _xmppClient(), - _xmppMUCManager() { + _xmppMUCManager() +{ AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); @@ -49,8 +50,7 @@ void XmppClient::connectToServer() { _xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password); } -void XmppClient::disconnectFromServer() -{ +void XmppClient::disconnectFromServer() { if (_xmppClient.isConnected()) { _xmppClient.disconnectFromServer(); } diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 3d0c140b0d..c857ddc8c9 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -15,13 +15,13 @@ #include #include -#include "ChatWindow.h" -#include "ui_chatWindow.h" +#include "Application.h" #include "FlowLayout.h" #include "qtimespan.h" +#include "ui_chatWindow.h" +#include "XmppClient.h" -#include -#include +#include "ChatWindow.h" const int NUM_MESSAGES_TO_TIME_STAMP = 20; @@ -30,7 +30,8 @@ const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); ChatWindow::ChatWindow() : QDialog(Application::getInstance()->getGLWidget(), Qt::Tool), ui(new Ui::ChatWindow), - numMessagesAfterLastTimeStamp(0) { + numMessagesAfterLastTimeStamp(0) +{ ui->setupUi(this); FlowLayout* flowLayout = new FlowLayout(0, 4, 4); @@ -47,8 +48,7 @@ ChatWindow::ChatWindow() : connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); ui->connectingToXMPPLabel->hide(); startTimerForTimeStamps(); - } - else { + } else { ui->numOnlineLabel->hide(); ui->usersWidget->hide(); ui->messagesScrollArea->hide(); @@ -118,8 +118,7 @@ void ChatWindow::addTimeStamp() { numMessagesAfterLastTimeStamp = 0; } -void ChatWindow::startTimerForTimeStamps() -{ +void ChatWindow::startTimerForTimeStamps() { QTimer* timer = new QTimer(this); timer->setInterval(10 * 60 * 1000); connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); @@ -191,8 +190,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { ++numMessagesAfterLastTimeStamp; if (message.stamp().isValid()) { lastMessageStamp = message.stamp().toLocalTime(); - } - else { + } else { lastMessageStamp = QDateTime::currentDateTime(); } } diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index be18a3ba45..3af7272cc1 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -32,4 +32,4 @@ if (UNIX AND NOT APPLE) target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") endif (UNIX AND NOT APPLE) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Xml) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) diff --git a/libraries/shared/src/DataServerAccountInfo.cpp b/libraries/shared/src/DataServerAccountInfo.cpp index 2bcd2dbcd0..1884517515 100644 --- a/libraries/shared/src/DataServerAccountInfo.cpp +++ b/libraries/shared/src/DataServerAccountInfo.cpp @@ -56,12 +56,9 @@ void DataServerAccountInfo::setUsername(const QString& username) { } } -void DataServerAccountInfo::setXMPPPassword(const QString& xmppPassword) -{ +void DataServerAccountInfo::setXMPPPassword(const QString& xmppPassword) { if (_xmppPassword != xmppPassword) { _xmppPassword = xmppPassword; - - qDebug() << "XMPP password changed to " << xmppPassword; } } @@ -73,4 +70,4 @@ QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword; return in; -} \ No newline at end of file +} From ed90e45c9640c0efff77591137dddf7ba032d229 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 15:46:07 +0200 Subject: [PATCH 08/13] Ensured messages are always aligned at the top to user names, and users onine are deleted. --- interface/src/ui/ChatWindow.cpp | 7 +++++-- interface/ui/chatWindow.ui | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index c857ddc8c9..ef379a086c 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -153,6 +153,7 @@ void ChatWindow::participantsChanged() { ui->numOnlineLabel->setText(tr("%1 online now:").arg(participants.count())); while (QLayoutItem* item = ui->usersWidget->layout()->takeAt(0)) { + delete item->widget(); delete item; } foreach (const QString& participant, participants) { @@ -173,13 +174,15 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { font.setBold(true); userLabel->setFont(font); userLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - userLabel->setStyleSheet("padding: 4px;"); + userLabel->setStyleSheet("padding: 2px;"); + userLabel->setAlignment(Qt::AlignTop); QLabel* messageLabel = new QLabel(message.body().replace(regexLinks, "\\1")); messageLabel->setWordWrap(true); messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); messageLabel->setOpenExternalLinks(true); - messageLabel->setStyleSheet("padding: 4px;"); + messageLabel->setStyleSheet("padding: 2px;"); + messageLabel->setAlignment(Qt::AlignTop); ui->messagesFormLayout->addRow(userLabel, messageLabel); ui->messagesFormLayout->parentWidget()->updateGeometry(); diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index 1723e72665..bebfc12674 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -95,7 +95,7 @@ 0 - 4 + 0 4 From e10dacdbfe1ec99662f21987a42c04090db4274c Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 17:50:51 +0200 Subject: [PATCH 09/13] Changed the searcning for qxmpp to include debug libraries as well. --- cmake/modules/FindQxmpp.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindQxmpp.cmake b/cmake/modules/FindQxmpp.cmake index 13ff1ed855..415b184a65 100644 --- a/cmake/modules/FindQxmpp.cmake +++ b/cmake/modules/FindQxmpp.cmake @@ -21,7 +21,7 @@ else () find_path(QXMPP_INCLUDE_DIR QXmppClient.h PATH_SUFFIXES include/qxmpp HINTS ${QXMPP_SEARCH_DIRS}) - find_library(QXMPP_LIBRARY NAMES qxmpp qxmpp0 PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS}) + find_library(QXMPP_LIBRARY NAMES qxmpp qxmpp0 qxmpp_d PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(QXMPP DEFAULT_MSG QXMPP_INCLUDE_DIR QXMPP_LIBRARY) From 858e9fa7df9b1376fd0642213f7e93265a8f1b40 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 18:54:39 +0200 Subject: [PATCH 10/13] Ensured sent chat messages are always wholly visible. --- interface/src/ui/ChatWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index ef379a086c..0ebfb3d4dd 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -189,6 +189,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { Application::processEvents(); QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); + messageLabel->updateGeometry(); ++numMessagesAfterLastTimeStamp; if (message.stamp().isValid()) { From fe2f9f52534729f0aa9d373c0ce05736a45b9415 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 19:26:14 +0200 Subject: [PATCH 11/13] Prevented empty time stamps - when less then a minute has passed - from appearing. --- interface/interface_en.ts | 2 +- interface/src/ui/ChatWindow.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index fbbe4752ea..ed8a2155e4 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -76,7 +76,7 @@ - + %1 online now: diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 0ebfb3d4dd..09b50ebf46 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -109,13 +109,15 @@ void ChatWindow::addTimeStamp() { } } timeString.chop(1); - QLabel* timeLabel = new QLabel(timeString); - timeLabel->setStyleSheet("color: palette(shadow);" - "background-color: palette(highlight);" - "padding: 4px;"); - timeLabel->setAlignment(Qt::AlignHCenter); - ui->messagesFormLayout->addRow(timeLabel); - numMessagesAfterLastTimeStamp = 0; + if (!timeString.isEmpty()) { + QLabel* timeLabel = new QLabel(timeString); + timeLabel->setStyleSheet("color: palette(shadow);" + "background-color: palette(highlight);" + "padding: 4px;"); + timeLabel->setAlignment(Qt::AlignHCenter); + ui->messagesFormLayout->addRow(timeLabel); + numMessagesAfterLastTimeStamp = 0; + } } void ChatWindow::startTimerForTimeStamps() { From 52c8ccb6981dcde6b01a9e39054b21cbb8505588 Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Tue, 11 Mar 2014 20:14:31 +0200 Subject: [PATCH 12/13] Added a define to ensure the build works without qxmpp. --- interface/CMakeLists.txt | 14 +++++++++----- interface/src/Menu.cpp | 6 ++++++ interface/src/XmppClient.cpp | 4 ++++ interface/src/XmppClient.h | 4 ++++ interface/src/ui/ChatWindow.cpp | 18 ++++++++++++++---- interface/src/ui/ChatWindow.h | 8 ++++++++ 6 files changed, 45 insertions(+), 9 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 2559c00207..dd4e1dca18 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -67,10 +67,6 @@ endforeach(EXTERNAL_SOURCE_SUBDIR) find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) -find_package(Qxmpp REQUIRED) -add_definitions(-DQXMPP_STATIC) -include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) - # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) # have qt5 wrap them and generate the appropriate header files @@ -132,6 +128,7 @@ find_package(LibOVR) find_package(Sixense) find_package(Visage) find_package(ZLIB) +find_package(Qxmpp) # include the Sixense library for Razer Hydra if available if (SIXENSE_FOUND AND NOT DISABLE_SIXENSE) @@ -171,6 +168,14 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}") endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) +# and with qxmpp for chat +if (QXMPP_FOUND AND NOT DISABLE_QXMPP) + add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC) + include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) + + target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}") +endif (QXMPP_FOUND AND NOT DISABLE_QXMPP) + # include headers for interface and InterfaceConfig. include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes") @@ -182,7 +187,6 @@ target_link_libraries( ${TARGET_NAME} "${FACESHIFT_LIBRARIES}" "${ZLIB_LIBRARIES}" - "${QXMPP_LIBRARY}" Qt5::Core Qt5::Gui Qt5::Multimedia Qt5::Network Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKit Qt5::WebKitWidgets Qt5::Xml Qt5::UiTools ) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2331a1bceb..5f6c541bc5 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -164,10 +164,14 @@ Menu::Menu() : addActionToQMenuAndActionHash(toolsMenu, MenuOption::FstUploader, 0, Application::getInstance(), SLOT(uploadFST())); _chatAction = addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, 0, this, SLOT(showChat())); +#ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); toggleChat(); connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat())); connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat())); +#else + _chatAction->setEnabled(false); +#endif QMenu* viewMenu = addMenu("View"); @@ -1046,10 +1050,12 @@ void Menu::showChat() { } void Menu::toggleChat() { +#ifdef HAVE_QXMPP _chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected()); if (!_chatAction->isEnabled() && _chatWindow) { _chatWindow->close(); } +#endif } void Menu::audioMuteToggled() { diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index de0a47c28a..df8cf34874 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -6,6 +6,8 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#ifdef HAVE_QXMPP + #include #include "XmppClient.h" @@ -63,3 +65,5 @@ XmppClient::XmppClient(const XmppClient& other) { void XmppClient::operator =(XmppClient const& other) { Q_UNUSED(other); } + +#endif diff --git a/interface/src/XmppClient.h b/interface/src/XmppClient.h index 1ebe868424..905d6e64fb 100644 --- a/interface/src/XmppClient.h +++ b/interface/src/XmppClient.h @@ -6,6 +6,8 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#ifdef HAVE_QXMPP + #ifndef __interface__XmppClient__ #define __interface__XmppClient__ @@ -41,3 +43,5 @@ private: }; #endif // __interface__XmppClient__ + +#endif diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 09b50ebf46..e7b78a897a 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -40,7 +40,7 @@ ChatWindow::ChatWindow() : ui->messagePlainTextEdit->installEventFilter(this); setAttribute(Qt::WA_DeleteOnClose); - +#ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); if (xmppClient.isConnected()) { participantsChanged(); @@ -56,16 +56,18 @@ ChatWindow::ChatWindow() : connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); } connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); +#endif } ChatWindow::~ChatWindow() { +#ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); disconnect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); disconnect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); disconnect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); - +#endif delete ui; } @@ -80,12 +82,14 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { (keyEvent->modifiers() & Qt::ShiftModifier) == 0) { QString messageText = ui->messagePlainTextEdit->document()->toPlainText().trimmed(); if (!messageText.isEmpty()) { +#ifdef HAVE_QXMPP const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); QXmppMessage message; message.setTo(publicChatRoom->jid()); message.setType(QXmppMessage::GroupChat); message.setBody(messageText); XmppClient::getInstance().getXMPPClient().sendPacket(message); +#endif ui->messagePlainTextEdit->document()->clear(); } return true; @@ -93,10 +97,12 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { return false; } +#ifdef HAVE_QXMPP QString ChatWindow::getParticipantName(const QString& participant) { const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); return participant.right(participant.count() - 1 - publicChatRoom->jid().count()); } +#endif void ChatWindow::addTimeStamp() { QTimeSpan timePassed = QDateTime::currentDateTime() - lastMessageStamp; @@ -133,10 +139,10 @@ void ChatWindow::connected() { ui->usersWidget->show(); ui->messagesScrollArea->show(); ui->messagePlainTextEdit->show(); - +#ifdef HAVE_QXMPP const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); - +#endif startTimerForTimeStamps(); } @@ -146,6 +152,8 @@ void ChatWindow::timeout() { } } +#ifdef HAVE_QXMPP + void ChatWindow::error(QXmppClient::Error error) { ui->connectingToXMPPLabel->setText(QString::number(error)); } @@ -200,3 +208,5 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { lastMessageStamp = QDateTime::currentDateTime(); } } + +#endif diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 16f8928d7e..9250da8933 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -15,9 +15,13 @@ #include +#ifdef HAVE_QXMPP + #include #include +#endif + namespace Ui { class ChatWindow; } @@ -33,7 +37,9 @@ protected: bool eventFilter(QObject* sender, QEvent* event); private: +#ifdef HAVE_QXMPP QString getParticipantName(const QString& participant); +#endif void startTimerForTimeStamps(); void addTimeStamp(); @@ -44,9 +50,11 @@ private: private slots: void connected(); void timeout(); +#ifdef HAVE_QXMPP void error(QXmppClient::Error error); void participantsChanged(); void messageReceived(const QXmppMessage& message); +#endif }; #endif /* defined(__interface__ChatWindow__) */ From f427f524bfce1d11c5ace7a2da951cf5bc2f4aea Mon Sep 17 00:00:00 2001 From: Dimitar Dobrev Date: Wed, 12 Mar 2014 00:01:27 +0200 Subject: [PATCH 13/13] Patched the Qt CMake function for auto-translations so that *.ts files are not deleted on clean. --- .../modules/FindQt5LinguistToolsMacros.cmake | 114 ++++++++++++++++++ interface/CMakeLists.txt | 7 +- interface/interface_en.ts | 16 +-- 3 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 cmake/modules/FindQt5LinguistToolsMacros.cmake diff --git a/cmake/modules/FindQt5LinguistToolsMacros.cmake b/cmake/modules/FindQt5LinguistToolsMacros.cmake new file mode 100644 index 0000000000..cd2d1eb74f --- /dev/null +++ b/cmake/modules/FindQt5LinguistToolsMacros.cmake @@ -0,0 +1,114 @@ +#============================================================================= +# Copyright 2005-2011 Kitware, Inc. +# All rights reserved. +# +# 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 Kitware, Inc. 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 +# HOLDER 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. +#============================================================================= + + + +function(QT5_CREATE_TRANSLATION_CUSTOM _qm_files) + set(options) + set(oneValueArgs) + set(multiValueArgs OPTIONS) + + cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(_lupdate_files ${_LUPDATE_UNPARSED_ARGUMENTS}) + set(_lupdate_options ${_LUPDATE_OPTIONS}) + + set(_my_sources) + set(_my_tsfiles) + foreach(_file ${_lupdate_files}) + get_filename_component(_ext ${_file} EXT) + get_filename_component(_abs_FILE ${_file} ABSOLUTE) + if(_ext MATCHES "ts") + list(APPEND _my_tsfiles ${_abs_FILE}) + else() + list(APPEND _my_sources ${_abs_FILE}) + endif() + endforeach() + set(_my_temptsfiles) + foreach(_ts_file ${_my_tsfiles}) + if(_my_sources) + # make a list file to call lupdate on, so we don't make our commands too + # long for some systems + get_filename_component(_ts_name ${_ts_file} NAME_WE) + set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file") + set(_lst_file_srcs) + foreach(_lst_file_src ${_my_sources}) + set(_lst_file_srcs "${_lst_file_src}\n${_lst_file_srcs}") + endforeach() + + get_directory_property(_inc_DIRS INCLUDE_DIRECTORIES) + foreach(_pro_include ${_inc_DIRS}) + get_filename_component(_abs_include "${_pro_include}" ABSOLUTE) + set(_lst_file_srcs "-I${_pro_include}\n${_lst_file_srcs}") + endforeach() + + file(WRITE ${_ts_lst_file} "${_lst_file_srcs}") + endif() + get_filename_component(_ts_nm ${_ts_file} NAME) + set(_tmpts_file "${CMAKE_CURRENT_BINARY_DIR}/${_ts_nm}") + list(APPEND _my_temptsfiles ${_tmpts_file}) + get_source_file_property(_qm_output_location ${_ts_file} OUTPUT_LOCATION) + add_custom_command( + OUTPUT ${_tmpts_file} + COMMAND ${Qt5_LUPDATE_EXECUTABLE} + ARGS ${_lupdate_options} "@${_ts_lst_file}" -ts ${_ts_file} + COMMAND ${CMAKE_COMMAND} -E copy ${_ts_file} ${_tmpts_file} + DEPENDS ${_my_sources} ${_ts_lst_file} VERBATIM) + if( _qm_output_location ) + set_property(SOURCE ${_tmpts_file} PROPERTY OUTPUT_LOCATION ${_qm_output_location}) + endif() + endforeach() + qt5_add_translation(${_qm_files} ${_my_temptsfiles}) + set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) +endfunction() + + +function(QT5_ADD_TRANSLATION _qm_files) + foreach(_current_FILE ${ARGN}) + get_filename_component(_abs_FILE ${_current_FILE} ABSOLUTE) + get_filename_component(qm ${_abs_FILE} NAME_WE) + get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION) + if(output_location) + file(MAKE_DIRECTORY "${output_location}") + set(qm "${output_location}/${qm}.qm") + else() + set(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm") + endif() + + add_custom_command(OUTPUT ${qm} + COMMAND ${Qt5_LRELEASE_EXECUTABLE} + ARGS ${_abs_FILE} -qm ${qm} + DEPENDS ${_abs_FILE} VERBATIM + ) + list(APPEND ${_qm_files} ${qm}) + endforeach() + set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) +endfunction() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index dd4e1dca18..d049fc0154 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -10,8 +10,6 @@ set(MACRO_DIR "${ROOT_DIR}/cmake/macros") set(TARGET_NAME interface) project(${TARGET_NAME}) -find_package(Qt5LinguistTools REQUIRED) - # setup for find modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") set(FACESHIFT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/faceshift") @@ -19,6 +17,9 @@ set(LIBOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/oculus") set(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense") set(VISAGE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/visage") +find_package(Qt5LinguistTools REQUIRED) +find_package(Qt5LinguistToolsMacros) + if (DEFINED ENV{JOB_ID}) set(BUILD_SEQ $ENV{JOB_ID}) else () @@ -77,7 +78,7 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}") set(QM ${TARGET_NAME}_en.qm) set(TS ${TARGET_NAME}_en.ts) -qt5_create_translation(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) +qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) if (APPLE) # configure CMake to use a custom Info.plist diff --git a/interface/interface_en.ts b/interface/interface_en.ts index ed8a2155e4..13ed5b968b 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -45,7 +45,7 @@ - + day %n day @@ -53,7 +53,7 @@ - + hour %n hour @@ -61,7 +61,7 @@ - + minute %n minute @@ -76,7 +76,7 @@ - + %1 online now: @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file