diff options
author | Cătălin Badea <catalin.badea392@gmail.com> | 2012-08-21 19:06:05 (GMT) |
---|---|---|
committer | Kevin Smith <git@kismith.co.uk> | 2012-08-30 20:54:18 (GMT) |
commit | 7d0cd94de71d9e55e573e28206470439ecde3db5 (patch) | |
tree | c361caa96dac71d53a74ba76aa3dc1d349a0c59e /Swift/QtUI | |
parent | 6856199274e9c5e581220fccf520b8f011519d17 (diff) | |
download | swift-contrib-7d0cd94de71d9e55e573e28206470439ecde3db5.zip swift-contrib-7d0cd94de71d9e55e573e28206470439ecde3db5.tar.bz2 |
History dialog
Add history dialog as an experimental feature.
License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.
Diffstat (limited to 'Swift/QtUI')
-rw-r--r-- | Swift/QtUI/MessageSnippet.cpp | 1 | ||||
-rw-r--r-- | Swift/QtUI/QtChatView.cpp | 94 | ||||
-rw-r--r-- | Swift/QtUI/QtChatView.h | 18 | ||||
-rw-r--r-- | Swift/QtUI/QtChatWindow.cpp | 12 | ||||
-rw-r--r-- | Swift/QtUI/QtHistoryWindow.cpp | 250 | ||||
-rw-r--r-- | Swift/QtUI/QtHistoryWindow.h | 66 | ||||
-rw-r--r-- | Swift/QtUI/QtHistoryWindow.ui | 101 | ||||
-rw-r--r-- | Swift/QtUI/QtMainWindow.cpp | 10 | ||||
-rw-r--r-- | Swift/QtUI/QtMainWindow.h | 1 | ||||
-rw-r--r-- | Swift/QtUI/QtUIFactory.cpp | 21 | ||||
-rw-r--r-- | Swift/QtUI/QtUIFactory.h | 4 | ||||
-rw-r--r-- | Swift/QtUI/QtUISettingConstants.cpp | 1 | ||||
-rw-r--r-- | Swift/QtUI/QtUISettingConstants.h | 1 | ||||
-rw-r--r-- | Swift/QtUI/SConscript | 2 |
14 files changed, 570 insertions, 12 deletions
diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp index 7505905..a10ee2c 100644 --- a/Swift/QtUI/MessageSnippet.cpp +++ b/Swift/QtUI/MessageSnippet.cpp @@ -37,6 +37,7 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); content_.replace("%userIconPath%", escape(iconURI)); content_ = "<div id='" + id + "'>" + content_ + "</div>"; + content_ = "<span class='date" + time.date().toString(Qt::ISODate) + "'>" + content_ + "</span>"; } MessageSnippet::~MessageSnippet() { diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 49e5974..eaec3b6 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -28,7 +28,7 @@ namespace Swift { -QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), fontSizeSteps_(0) { +QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QWidget(parent), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll) { theme_ = theme; QVBoxLayout* mainLayout = new QVBoxLayout(this); @@ -61,6 +61,7 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), f //webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); webView_->setPage(webPage_); connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); + connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&))); viewReady_ = false; isAtBottom_ = true; @@ -85,7 +86,7 @@ void QtChatView::handleKeyPressEvent(QKeyEvent* event) { webView_->keyPressEvent(event); } -void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) { +void QtChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) { if (viewReady_) { addToDOM(snippet); } else { @@ -94,6 +95,45 @@ void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) { } } +void QtChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) { + // save scrollbar maximum value + if (!topMessageAdded_) { + scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); + } + topMessageAdded_ = true; + + QWebElement continuationElement = firstElement_.findFirst("#insert"); + + bool insert = snippet->getAppendToPrevious(); + bool fallback = continuationElement.isNull(); + + boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet; + QWebElement newElement = snippetToDOM(newSnippet); + + if (insert && !fallback) { + Q_ASSERT(!continuationElement.isNull()); + continuationElement.replace(newElement); + } else { + continuationElement.removeFromDocument(); + topInsertPoint_.prependOutside(newElement); + } + + firstElement_ = newElement; + + if (lastElement_.isNull()) { + lastElement_ = firstElement_; + } + + if (fontSizeSteps_ != 0) { + double size = 1.0 + 0.2 * fontSizeSteps_; + QString sizeString(QString().setNum(size, 'g', 3) + "em"); + const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable"); + foreach (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + } +} + QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) { QWebElement newElement = newInsertPoint_.clone(); newElement.setInnerXml(snippet->getContent()); @@ -230,7 +270,7 @@ void QtChatView::displayReceiptInfo(const QString& id, bool showIt) { } void QtChatView::rememberScrolledToBottom() { - isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); + isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1); } void QtChatView::scrollToBottom() { @@ -240,7 +280,14 @@ void QtChatView::scrollToBottom() { } void QtChatView::handleFrameSizeChanged() { - if (isAtBottom_) { + if (topMessageAdded_) { + // adjust new scrollbar position + int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); + webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_); + topMessageAdded_ = false; + } + + if (isAtBottom_ && !disableAutoScroll_) { scrollToBottom(); } } @@ -282,6 +329,9 @@ void QtChatView::resizeFont(int fontSizeSteps) { void QtChatView::resetView() { lastElement_ = QWebElement(); + firstElement_ = lastElement_; + topMessageAdded_ = false; + scrollBarMaximum_ = 0; QString pageHTML = theme_->getTemplate(); pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3"); pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase()); @@ -302,12 +352,16 @@ void QtChatView::resetView() { syncLoop.exec(); } document_ = webPage_->mainFrame()->documentElement(); + + resetTopInsertPoint(); QWebElement chatElement = document_.findFirst("#Chat"); newInsertPoint_ = chatElement.clone(); newInsertPoint_.setOuterXml("<div id='swift_insert'/>"); chatElement.appendInside(newInsertPoint_); Q_ASSERT(!newInsertPoint_.isNull()); + scrollToBottom(); + connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); } @@ -384,4 +438,36 @@ void QtChatView::setMUCInvitationJoined(QString id) { } } +void QtChatView::handleScrollRequested(int, int dy, const QRect&) { + rememberScrolledToBottom(); + + int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy; + emit scrollRequested(pos); + + if (pos == 0) { + emit scrollReachedTop(); + } + else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) { + emit scrollReachedBottom(); + } +} + +int QtChatView::getSnippetPositionByDate(const QDate& date) { + QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate)); + + return message.geometry().top(); +} + +void QtChatView::resetTopInsertPoint() { + QWebElement continuationElement = firstElement_.findFirst("#insert"); + continuationElement.removeFromDocument(); + firstElement_ = QWebElement(); + + topInsertPoint_.removeFromDocument(); + QWebElement chatElement = document_.findFirst("#Chat"); + topInsertPoint_ = chatElement.clone(); + topInsertPoint_.setOuterXml("<div id='swift_insert'/>"); + chatElement.prependInside(topInsertPoint_); +} + } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index fdbdd5a..118f14b 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -20,6 +20,7 @@ class QWebPage; class QUrl; +class QDate; namespace Swift { class QtWebView; @@ -27,8 +28,9 @@ namespace Swift { class QtChatView : public QWidget { Q_OBJECT public: - QtChatView(QtChatTheme* theme, QWidget* parent); - void addMessage(boost::shared_ptr<ChatSnippet> snippet); + QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false); + void addMessageTop(boost::shared_ptr<ChatSnippet> snippet); + void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet); void addLastSeenLine(); void replaceLastMessage(const QString& newMessage); void replaceLastMessage(const QString& newMessage, const QString& note); @@ -44,10 +46,15 @@ namespace Swift { void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); void setMUCInvitationJoined(QString id); void showEmoticons(bool show); + int getSnippetPositionByDate(const QDate& date); + signals: void gotFocus(); void fontResized(int); void logCleared(); + void scrollRequested(int pos); + void scrollReachedTop(); + void scrollReachedBottom(); public slots: void copySelectionToClipboard(); @@ -55,6 +62,7 @@ namespace Swift { void handleLinkClicked(const QUrl&); void handleKeyPressEvent(QKeyEvent* event); void resetView(); + void resetTopInsertPoint(); void increaseFontSize(int numSteps = 1); void decreaseFontSize(); void resizeFont(int fontSizeSteps); @@ -63,6 +71,7 @@ namespace Swift { void handleViewLoadFinished(bool); void handleFrameSizeChanged(); void handleClearRequested(); + void handleScrollRequested(int dx, int dy, const QRect& rectToScroll); private: void headerEncode(); @@ -72,14 +81,19 @@ namespace Swift { bool viewReady_; bool isAtBottom_; + bool topMessageAdded_; + int scrollBarMaximum_; QtWebView* webView_; QWebPage* webPage_; int fontSizeSteps_; QtChatTheme* theme_; QWebElement newInsertPoint_; + QWebElement topInsertPoint_; QWebElement lineSeparator_; QWebElement lastElement_; + QWebElement firstElement_; QWebElement document_; + bool disableAutoScroll_; }; } diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index f42469b..ddfe158 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -524,7 +524,7 @@ std::string QtChatWindow::addMessage(const QString &message, const std::string & } QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); previousMessageWasSelf_ = senderIsSelf; previousSenderName_ = P2QSTRING(senderName); @@ -633,7 +633,7 @@ std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool se } QString qAvatarPath = "qrc:/icons/avatar.png"; std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); previousMessageWasSelf_ = senderIsSelf; previousSenderName_ = P2QSTRING(senderName); @@ -701,7 +701,7 @@ void QtChatWindow::addErrorMessage(const std::string& errorMessage) { QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage))); errorMessageHTML.replace("\n","<br/>"); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_))); + messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_))); previousMessageWasSelf_ = false; previousMessageKind_ = PreviousMessageWasSystem; @@ -714,7 +714,7 @@ void QtChatWindow::addSystemMessage(const std::string& message) { QString messageHTML(P2QSTRING(message)); messageHTML = linkimoticonify(messageHTML); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); + messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); previousMessageKind_ = PreviousMessageWasSystem; } @@ -753,7 +753,7 @@ void QtChatWindow::addPresenceMessage(const std::string& message) { QString messageHTML(P2QSTRING(message)); messageHTML = linkimoticonify(messageHTML); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); + messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); previousMessageKind_ = PreviousMessageWasPresence; } @@ -950,7 +950,7 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji } QString qAvatarPath = "qrc:/icons/avatar.png"; - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id))); + messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id))); previousMessageWasSelf_ = false; previousSenderName_ = P2QSTRING(senderName); previousMessageKind_ = PreviousMessageWasMUCInvite; diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp new file mode 100644 index 0000000..e54bd51 --- /dev/null +++ b/Swift/QtUI/QtHistoryWindow.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <QtHistoryWindow.h> +#include <QtTabbable.h> + +#include <QtSwiftUtil.h> +#include <MessageSnippet.h> +#include <Swiften/History/HistoryMessage.h> +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <QTime> +#include <QUrl> +#include <QMenu> +#include <QTextDocument> +#include <QDateTime> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <QLineEdit> + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/date_time/gregorian/gregorian.hpp> + +namespace Swift { + +QtHistoryWindow::QtHistoryWindow(SettingsProvider* settings, UIEventStream* eventStream) : + previousTopMessageWasSelf_(false), + previousBottomMessageWasSelf_(false) { + ui_.setupUi(this); + + theme_ = new QtChatTheme(""); + idCounter_ = 0; + + delete ui_.conversation_; + conversation_ = new QtChatView(theme_, this, true); + QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(80); + sizePolicy.setVerticalStretch(0); + conversation_->setSizePolicy(sizePolicy); + + ui_.conversation_ = conversation_; + ui_.bottomLayout_->addWidget(conversation_); + + delete ui_.conversationRoster_; + conversationRoster_ = new QtTreeWidget(eventStream, settings, this); + QSizePolicy sizePolicy2(QSizePolicy::Preferred, QSizePolicy::Expanding); + sizePolicy2.setVerticalStretch(80); + conversationRoster_->setSizePolicy(sizePolicy2); + ui_.conversationRoster_ = conversationRoster_; + ui_.bottomLeftLayout_->setDirection(QBoxLayout::BottomToTop); + ui_.bottomLeftLayout_->addWidget(conversationRoster_); + + setWindowTitle(tr("History")); + + conversationRoster_->onSomethingSelectedChanged.connect(boost::bind(&QtHistoryWindow::handleSomethingSelectedChanged, this, _1)); + connect(conversation_, SIGNAL(scrollRequested(int)), this, SLOT(handleScrollRequested(int))); + connect(conversation_, SIGNAL(scrollReachedTop()), this, SLOT(handleScrollReachedTop())); + connect(conversation_, SIGNAL(scrollReachedBottom()), this, SLOT(handleScrollReachedBottom())); + connect(conversation_, SIGNAL(fontResized(int)), this, SLOT(handleFontResized(int))); + connect(ui_.searchBox_->lineEdit(), SIGNAL(returnPressed()), this, SLOT(handleReturnPressed())); + connect(ui_.calendarWidget_, SIGNAL(clicked(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + connect(ui_.calendarWidget_, SIGNAL(activated(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + connect(ui_.previousButton_, SIGNAL(clicked(bool)), this, SLOT(handlePreviousButtonClicked())); + connect(ui_.nextButton_, SIGNAL(clicked(bool)), this, SLOT(handleNextButtonClicked())); +} + +QtHistoryWindow::~QtHistoryWindow() { + disconnect(conversation_, SIGNAL(scrollRequested(int)), this, SLOT(handleScrollRequested(int))); + disconnect(conversation_, SIGNAL(scrollReachedTop()), this, SLOT(handleScrollReachedTop())); + disconnect(conversation_, SIGNAL(scrollReachedBottom()), this, SLOT(handleScrollReachedBottom())); + disconnect(conversation_, SIGNAL(fontResized(int)), this, SLOT(handleFontResized(int))); + disconnect(ui_.searchBox_->lineEdit(), SIGNAL(returnPressed()), this, SLOT(handleReturnPressed())); + disconnect(ui_.calendarWidget_, SIGNAL(clicked(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + disconnect(ui_.calendarWidget_, SIGNAL(activated(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + disconnect(ui_.previousButton_, SIGNAL(clicked(bool)), this, SLOT(handlePreviousButtonClicked())); + disconnect(ui_.nextButton_, SIGNAL(clicked(bool)), this, SLOT(handleNextButtonClicked())); + + delete theme_; + delete conversation_; + // TODO: delete ui_ +} + +void QtHistoryWindow::activate() { + emit wantsToActivate(); +} + +void QtHistoryWindow::showEvent(QShowEvent* event) { + emit windowOpening(); + emit titleUpdated(); + QWidget::showEvent(event); +} + +void QtHistoryWindow::closeEvent(QCloseEvent* event) { + emit windowClosing(); + event->accept(); +} + +void QtHistoryWindow::setRosterModel(Roster* model) { + conversationRoster_->setRosterModel(model); +} + +void QtHistoryWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop) { + QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); + + QString messageHTML(P2QSTRING(message)); + messageHTML = Qt::escape(messageHTML); + QString searchTerm = ui_.searchBox_->lineEdit()->text(); + if (searchTerm.length()) { + messageHTML.replace(searchTerm, "<span style='background-color: yellow'>" + searchTerm + "</span>"); + } + + // note: time uses localtime + QDate date = QDate(time.date().year(), time.date().month(), time.date().day()); + QTime dayTime = QTime(time.time_of_day().hours(), time.time_of_day().minutes(), time.time_of_day().seconds()); + QDateTime qTime = QDateTime(date, dayTime); + + std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + + QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); + + if (addAtTheTop) { + bool appendToPrevious = ((senderIsSelf && previousTopMessageWasSelf_) || (!senderIsSelf && !previousTopMessageWasSelf_&& previousTopSenderName_ == P2QSTRING(senderName))); + conversation_->addMessageTop(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, Qt::escape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + + previousTopMessageWasSelf_ = senderIsSelf; + previousTopSenderName_ = P2QSTRING(senderName); + } + else { + bool appendToPrevious = ((senderIsSelf && previousBottomMessageWasSelf_) || (!senderIsSelf && !previousBottomMessageWasSelf_&& previousBottomSenderName_ == P2QSTRING(senderName))); + conversation_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, Qt::escape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + previousBottomMessageWasSelf_ = senderIsSelf; + previousBottomSenderName_ = P2QSTRING(senderName); + } + + // keep track of the days viewable in the chatView + if (!dates_.count(date)) { + dates_.insert(date); + } +} + +void QtHistoryWindow::handleSomethingSelectedChanged(RosterItem* item) { + onSelectedContactChanged(item); +} + +void QtHistoryWindow::resetConversationView() { + previousTopMessageWasSelf_ = false; + previousBottomMessageWasSelf_ = false; + previousTopSenderName_.clear(); + previousBottomSenderName_.clear(); + + dates_.clear(); + conversation_->resetView(); +} + +void QtHistoryWindow::handleScrollRequested(int pos) { + // first message starts with offset 5 + if (pos < 5) { + pos = 5; + } + + QDate currentDate; + foreach (const QDate& date, dates_) { + int snippetPosition = conversation_->getSnippetPositionByDate(date); + if (snippetPosition <= pos) { + currentDate = date; + } + } + + if (ui_.calendarWidget_->selectedDate() != currentDate) { + ui_.calendarWidget_->setSelectedDate(currentDate); + } +} + +void QtHistoryWindow::handleScrollReachedTop() { + if (dates_.empty()) { + return; + } + + int year, month, day; + QDate firstDate = *dates_.begin(); + firstDate.getDate(&year, &month, &day); + onScrollReachedTop(boost::gregorian::date(year, month, day)); +} + +void QtHistoryWindow::handleScrollReachedBottom() { + if (dates_.empty()) { + return; + } + + int year, month, day; + QDate lastDate = *dates_.rbegin(); + lastDate.getDate(&year, &month, &day); + onScrollReachedBottom(boost::gregorian::date(year, month, day)); +} + +void QtHistoryWindow::handleReturnPressed() { + onReturnPressed(ui_.searchBox_->lineEdit()->text().toStdString()); +} + +void QtHistoryWindow::handleCalendarClicked(const QDate& date) { + int year, month, day; + QDate tempDate = date; // getDate discards const qualifier + tempDate.getDate(&year, &month, &day); + onCalendarClicked(boost::gregorian::date(year, month, day)); +} + +void QtHistoryWindow::setDate(const boost::gregorian::date& date) { + ui_.calendarWidget_->setSelectedDate(QDate::fromJulianDay(date.julian_day())); +} + +void QtHistoryWindow::handleNextButtonClicked() { + onNextButtonClicked(); +} + +void QtHistoryWindow::handlePreviousButtonClicked() { + onPreviousButtonClicked(); +} + +void QtHistoryWindow::handleFontResized(int fontSizeSteps) { + conversation_->resizeFont(fontSizeSteps); + + emit fontResized(fontSizeSteps); +} + +void QtHistoryWindow::resetConversationViewTopInsertPoint() { + previousTopMessageWasSelf_ = false; + previousTopSenderName_ = QString(); + conversation_->resetTopInsertPoint(); +} + +std::string QtHistoryWindow::getSearchBoxText() { + return ui_.searchBox_->lineEdit()->text().toStdString(); +} + +boost::gregorian::date QtHistoryWindow::getLastVisibleDate() { + if (!dates_.empty()) { + QDate lastDate = *dates_.rbegin(); + int year, month, day; + lastDate.getDate(&year, &month, &day); + + return boost::gregorian::date(year, month, day); + } + return boost::gregorian::date(boost::gregorian::not_a_date_time); +} + +} diff --git a/Swift/QtUI/QtHistoryWindow.h b/Swift/QtUI/QtHistoryWindow.h new file mode 100644 index 0000000..49de098 --- /dev/null +++ b/Swift/QtUI/QtHistoryWindow.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/HistoryWindow.h> +#include <Swift/QtUI/ui_QtHistoryWindow.h> +#include <QtChatView.h> +#include <QtTabbable.h> +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <set> +#include <QDate> + +namespace Swift { + class QtHistoryWindow : public QtTabbable, public HistoryWindow { + Q_OBJECT + + public: + QtHistoryWindow(SettingsProvider*, UIEventStream*); + ~QtHistoryWindow(); + void activate(); + void setRosterModel(Roster*); + void addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop); + void resetConversationView(); + void resetConversationViewTopInsertPoint(); + void setDate(const boost::gregorian::date& date); + + void closeEvent(QCloseEvent* event); + void showEvent(QShowEvent* event); + + std::string getSearchBoxText(); + boost::gregorian::date getLastVisibleDate(); + + signals: + void fontResized(int); + + public slots: + void handleFontResized(int fontSizeSteps); + + protected slots: + void handleScrollRequested(int pos); + void handleScrollReachedTop(); + void handleScrollReachedBottom(); + void handleReturnPressed(); + void handleCalendarClicked(const QDate& date); + void handlePreviousButtonClicked(); + void handleNextButtonClicked(); + + private: + void handleSomethingSelectedChanged(RosterItem* item); + + Ui::QtHistoryWindow ui_; + QtChatTheme* theme_; + QtChatView* conversation_; + QtTreeWidget* conversationRoster_; + std::set<QDate> dates_; + int idCounter_; + bool previousTopMessageWasSelf_; + QString previousTopSenderName_; + bool previousBottomMessageWasSelf_; + QString previousBottomSenderName_; + }; +} diff --git a/Swift/QtUI/QtHistoryWindow.ui b/Swift/QtUI/QtHistoryWindow.ui new file mode 100644 index 0000000..77d592f --- /dev/null +++ b/Swift/QtUI/QtHistoryWindow.ui @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHistoryWindow</class> + <widget class="QWidget" name="QtHistoryWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>608</width> + <height>522</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="topLayout_"> + <item> + <widget class="QLabel" name="label_"> + <property name="text"> + <string>Search:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="searchBox_"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="nextButton_"> + <property name="text"> + <string>Next</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="previousButton_"> + <property name="text"> + <string>Previous</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QSplitter" name="bottomLayout_"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QWidget" name="layoutWidget"> + <layout class="QVBoxLayout" name="bottomLeftLayout_" stretch="0,0"> + <item> + <widget class="QWidget" name="conversationRoster_" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>5</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QCalendarWidget" name="calendarWidget_"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="conversation_" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>85</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index d312546..ced375f 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -27,6 +27,7 @@ #include <Swift/QtUI/QtLoginWindow.h> #include <Roster/QtRosterWidget.h> #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> @@ -122,6 +123,11 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr QAction* joinMUCAction = new QAction(tr("Enter &Room…"), this); connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction())); actionsMenu->addAction(joinMUCAction); +#ifdef SWIFT_EXPERIMENTAL_HISTORY + QAction* viewLogsAction = new QAction(tr("&View History…"), this); + connect(viewLogsAction, SIGNAL(triggered()), SLOT(handleViewLogsAction())); + actionsMenu->addAction(viewLogsAction); +#endif addUserAction_ = new QAction(tr("&Add Contact…"), this); connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool))); actionsMenu->addAction(addUserAction_); @@ -235,6 +241,10 @@ void QtMainWindow::handleJoinMUCAction() { uiEventStream_->send(boost::make_shared<RequestJoinMUCUIEvent>()); } +void QtMainWindow::handleViewLogsAction() { + uiEventStream_->send(boost::make_shared<RequestHistoryUIEvent>()); +} + void QtMainWindow::handleStatusChanged(StatusShow::Type showType, const QString &statusMessage) { onChangeStatusRequest(showType, Q2PSTRING(statusMessage)); } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 251c346..26d25e1 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -58,6 +58,7 @@ namespace Swift { void handleShowOfflineToggled(bool); void handleShowEmoticonsToggled(bool); void handleJoinMUCAction(); + void handleViewLogsAction(); void handleSignOutAction(); void handleEditProfileAction(); void handleAddUserActionTriggered(bool checked); diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 78de7aa..2197ec6 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -27,11 +27,13 @@ #include "QtFileTransferListWidget.h" #include <Swift/Controllers/Settings/SettingsProviderHierachy.h> #include <Swift/QtUI/QtUISettingConstants.h> +#include <QtHistoryWindow.h> namespace Swift { QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) { chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); + historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); } XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { @@ -44,6 +46,25 @@ XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { return widget; } +HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) { + QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream); + tabs->addTab(window); + if (!tabs->isVisible()) { + tabs->show(); + } + + connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int))); + + window->handleFontResized(historyFontSize_); + window->show(); + return window; +} + +void QtUIFactory::handleHistoryWindowFontResized(int size) { + historyFontSize_ = size; + settings->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size); +} + FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { QtFileTransferListWidget* widget = new QtFileTransferListWidget(); tabs->addTab(widget); diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index edb89ad..1b2431f 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -24,6 +24,7 @@ namespace Swift { class QtChatWindowFactory; class QtChatWindow; class TimerFactory; + class historyWindow_; class QtUIFactory : public QObject, public UIFactory { Q_OBJECT @@ -31,6 +32,7 @@ namespace Swift { QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist); virtual XMLConsoleWidget* createXMLConsoleWidget(); + virtual HistoryWindow* createHistoryWindow(UIEventStream*); virtual MainWindow* createMainWindow(UIEventStream* eventStream); virtual LoginWindow* createLoginWindow(UIEventStream* eventStream); virtual EventWindow* createEventWindow(); @@ -47,6 +49,7 @@ namespace Swift { private slots: void handleLoginWindowGeometryChanged(); void handleChatWindowFontResized(int); + void handleHistoryWindowFontResized(int); private: SettingsProviderHierachy* settings; @@ -61,6 +64,7 @@ namespace Swift { std::vector<QPointer<QtChatWindow> > chatWindows; bool startMinimized; int chatFontSize; + int historyFontSize_; bool emoticonsExist_; }; } diff --git a/Swift/QtUI/QtUISettingConstants.cpp b/Swift/QtUI/QtUISettingConstants.cpp index 81022ec..68001d7 100644 --- a/Swift/QtUI/QtUISettingConstants.cpp +++ b/Swift/QtUI/QtUISettingConstants.cpp @@ -13,5 +13,6 @@ const SettingsProvider::Setting<std::string> QtUISettingConstants::CLICKTHROUGH_ const SettingsProvider::Setting<int> QtUISettingConstants::CURRENT_ROSTER_TAB("currentRosterTab", 0); const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER("showNickInRosterHeader", true); const SettingsProvider::Setting<int> QtUISettingConstants::CHATWINDOW_FONT_SIZE("chatWindowFontSize", 0); +const SettingsProvider::Setting<int> QtUISettingConstants::HISTORYWINDOW_FONT_SIZE("historyWindowFontSize", 0); const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_EMOTICONS("showEmoticons", true); } diff --git a/Swift/QtUI/QtUISettingConstants.h b/Swift/QtUI/QtUISettingConstants.h index 2740abb..8ac835f 100644 --- a/Swift/QtUI/QtUISettingConstants.h +++ b/Swift/QtUI/QtUISettingConstants.h @@ -16,6 +16,7 @@ namespace Swift { static const SettingsProvider::Setting<int> CURRENT_ROSTER_TAB; static const SettingsProvider::Setting<bool> SHOW_NICK_IN_ROSTER_HEADER; static const SettingsProvider::Setting<int> CHATWINDOW_FONT_SIZE; + static const SettingsProvider::Setting<int> HISTORYWINDOW_FONT_SIZE; static const SettingsProvider::Setting<bool> SHOW_EMOTICONS; }; } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 064faab..27ff237 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -94,6 +94,7 @@ sources = [ "QtTabWidget.cpp", "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", + "QtHistoryWindow.cpp", "QtFileTransferListWidget.cpp", "QtFileTransferListItemModel.cpp", "QtAdHocCommandWindow.cpp", @@ -198,6 +199,7 @@ myenv.Uic4("UserSearch/QtUserSearchResultsPage.ui") myenv.Uic4("QtBookmarkDetailWindow.ui") myenv.Uic4("QtAffiliationEditor.ui") myenv.Uic4("QtJoinMUCWindow.ui") +myenv.Uic4("QtHistoryWindow.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") |