diff options
26 files changed, 424 insertions, 12 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index edd2e3b..2d2d941 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -31,6 +31,7 @@ #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Idle.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Highlighter.h> #include <Swiften/Base/Log.h> @@ -62,6 +63,10 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact); } + Idle::ref idle; + if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { + startMessage += QT_TRANSLATE_NOOP("", ", who has been idle since ") + boost::posix_time::to_simple_string(idle->getSince()); + } startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); if (theirPresence && !theirPresence->getStatus().empty()) { startMessage += " (" + theirPresence->getStatus() + ")"; @@ -335,6 +340,11 @@ std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> pr response = QT_TRANSLATE_NOOP("", "%1% is now busy"); } } + Idle::ref idle; + if ((idle = presence->getPayload<Idle>())) { + response += QT_TRANSLATE_NOOP("", " and has been idle since ") + boost::posix_time::to_simple_string(idle->getSince()); + } + if (!response.empty()) { response = str(format(response) % nick); } diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 87ec94d..0c9c09c 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -461,7 +461,7 @@ void MainController::handleInputIdleChanged(bool idle) { } else { if (idle) { - if (statusTracker_->goAutoAway()) { + if (statusTracker_->goAutoAway(idleDetector_->getIdleTimeSeconds())) { if (client_ && client_->isAvailable()) { sendPresence(statusTracker_->getNextPresence()); } diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 5b1b6e0..d2edfe7 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -8,6 +8,9 @@ #include "Swift/Controllers/Roster/GroupRosterItem.h" #include <Swiften/Base/foreach.h> +#include <Swiften/Elements/Idle.h> + +#include <boost/date_time/posix_time/posix_time.hpp> namespace Swift { @@ -39,6 +42,15 @@ std::string ContactRosterItem::getStatusText() const { return shownPresence_ ? shownPresence_->getStatus() : ""; } +std::string ContactRosterItem::getIdleText() const { + Idle::ref idle = shownPresence_ ? shownPresence_->getPayload<Idle>() : Idle::ref(); + if (!idle || idle->getSince().is_not_a_date_time()) { + return ""; + } else { + return boost::posix_time::to_simple_string(idle->getSince()); + } +} + void ContactRosterItem::setAvatarPath(const std::string& path) { avatarPath_ = path; onDataChanged(); diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 8389a44..7a2100e 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -17,6 +17,7 @@ #include <boost/bind.hpp> #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/ptime.hpp> namespace Swift { @@ -35,6 +36,7 @@ class ContactRosterItem : public RosterItem { StatusShow::Type getStatusShow() const; StatusShow::Type getSimplifiedStatusShow() const; std::string getStatusText() const; + std::string getIdleText() const; void setAvatarPath(const std::string& path); const std::string& getAvatarPath() const; const JID& getJID() const; @@ -50,9 +52,11 @@ class ContactRosterItem : public RosterItem { void setSupportedFeatures(const std::set<Feature>& features); bool supportsFeature(Feature feature) const; + private: JID jid_; JID displayJID_; + boost::posix_time::ptime lastAvailableTime_; std::string avatarPath_; std::map<std::string, boost::shared_ptr<Presence> > presences_; boost::shared_ptr<Presence> offlinePresence_; diff --git a/Swift/Controllers/StatusTracker.cpp b/Swift/Controllers/StatusTracker.cpp index 0c88f4d..6766c2e 100644 --- a/Swift/Controllers/StatusTracker.cpp +++ b/Swift/Controllers/StatusTracker.cpp @@ -8,6 +8,8 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Elements/Idle.h> + namespace Swift { StatusTracker::StatusTracker() { @@ -21,6 +23,7 @@ boost::shared_ptr<Presence> StatusTracker::getNextPresence() { presence = boost::make_shared<Presence>(); presence->setShow(StatusShow::Away); presence->setStatus(queuedPresence_->getStatus()); + presence->addPayload(boost::make_shared<Idle>(isAutoAwaySince_)); } else { presence = queuedPresence_; } @@ -35,11 +38,12 @@ void StatusTracker::setRequestedPresence(boost::shared_ptr<Presence> presence) { // } } -bool StatusTracker::goAutoAway() { +bool StatusTracker::goAutoAway(const int& seconds) { if (queuedPresence_->getShow() != StatusShow::Online) { return false; } isAutoAway_ = true; + isAutoAwaySince_ = boost::posix_time::second_clock::universal_time() - boost::posix_time::seconds(seconds); return true; } diff --git a/Swift/Controllers/StatusTracker.h b/Swift/Controllers/StatusTracker.h index 4f4e880..10a5c0c 100644 --- a/Swift/Controllers/StatusTracker.h +++ b/Swift/Controllers/StatusTracker.h @@ -10,6 +10,8 @@ #include "Swiften/Elements/Presence.h" +#include <boost/date_time/posix_time/posix_time_types.hpp> + namespace Swift { class StatusTracker { @@ -17,10 +19,11 @@ namespace Swift { StatusTracker(); boost::shared_ptr<Presence> getNextPresence(); void setRequestedPresence(boost::shared_ptr<Presence> presence); - bool goAutoAway(); + bool goAutoAway(const int& seconds); bool goAutoUnAway(); private: boost::shared_ptr<Presence> queuedPresence_; bool isAutoAway_; + boost::posix_time::ptime isAutoAwaySince_; }; } diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp index 5b879df..5b03ac5 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.cpp +++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp @@ -120,7 +120,7 @@ void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem QString name = item->data(Qt::DisplayRole).toString(); //qDebug() << "Avatar for " << name << " = " << avatarPath; QString statusText = item->data(ChatListRecentItem::DetailTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, item->getChat().unreadCount, compact_); } void ChatListDelegate::paintWhiteboard(QPainter* painter, const QStyleOptionViewItem& option, ChatListWhiteboardItem* item) const { @@ -135,7 +135,8 @@ void ChatListDelegate::paintWhiteboard(QPainter* painter, const QStyleOptionView QString name = item->data(Qt::DisplayRole).toString(); //qDebug() << "Avatar for " << name << " = " << avatarPath; QString statusText = item->data(ChatListWhiteboardItem::DetailTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, item->getChat().unreadCount, compact_); + } } diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.h b/Swift/QtUI/ChatList/ChatListRecentItem.h index 4e7bc3e..3f27a68 100644 --- a/Swift/QtUI/ChatList/ChatListRecentItem.h +++ b/Swift/QtUI/ChatList/ChatListRecentItem.h @@ -23,7 +23,8 @@ namespace Swift { DetailTextRole = Qt::UserRole, AvatarRole = Qt::UserRole + 1, PresenceIconRole = Qt::UserRole + 2/*, - StatusShowTypeRole = Qt::UserRole + 3*/ + StatusShowTypeRole = Qt::UserRole + 3, + IdleRole = Qt::UserRole + 4*/ }; ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent); const ChatListWindow::Chat& getChat() const; diff --git a/Swift/QtUI/Roster/DelegateCommons.cpp b/Swift/QtUI/Roster/DelegateCommons.cpp index a575cb0..e7342f3 100644 --- a/Swift/QtUI/Roster/DelegateCommons.cpp +++ b/Swift/QtUI/Roster/DelegateCommons.cpp @@ -17,8 +17,8 @@ void DelegateCommons::drawElidedText(QPainter* painter, const QRect& region, con painter->drawText(region, flags, adjustedText.simplified()); } -void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount, bool compact) const { - painter->save(); +void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const { + painter->save(); QRect fullRegion(option.rect); if ( option.state & QStyle::State_Selected ) { painter->fillRect(fullRegion, option.palette.highlight()); @@ -29,6 +29,7 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin)); + QRect idleIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth*2, fullRegion.height() - verticalMargin)); int calculatedAvatarSize = presenceIconRegion.height(); //This overlaps the presenceIcon, so must be painted first QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize)); @@ -51,6 +52,10 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem //Paint the presence icon over the top of the avatar presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + if (isIdle) { + idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + } + QFontMetrics nameMetrics(nameFont); painter->setFont(nameFont); int extraFontWidth = nameMetrics.width("H"); diff --git a/Swift/QtUI/Roster/DelegateCommons.h b/Swift/QtUI/Roster/DelegateCommons.h index 8732598..0684410 100644 --- a/Swift/QtUI/Roster/DelegateCommons.h +++ b/Swift/QtUI/Roster/DelegateCommons.h @@ -17,7 +17,7 @@ namespace Swift { class DelegateCommons { public: - DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()) { + DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()), idleIcon(QIcon(":/icons/zzz.png")) { detailFontSizeDrop = nameFont.pointSize() >= 10 ? 2 : 0; detailFont.setStyle(QFont::StyleItalic); detailFont.setPointSize(nameFont.pointSize() - detailFontSizeDrop); @@ -26,7 +26,7 @@ namespace Swift { static void drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags = Qt::AlignTop); QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index, bool compact) const; - void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount, bool compact) const; + void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const; int detailFontSizeDrop; QFont nameFont; @@ -38,5 +38,6 @@ namespace Swift { static const int presenceIconHeight; static const int presenceIconWidth; static const int unreadCountSize; + QIcon idleIcon; }; } diff --git a/Swift/QtUI/Roster/RosterDelegate.cpp b/Swift/QtUI/Roster/RosterDelegate.cpp index 5c964ca..aef588c 100644 --- a/Swift/QtUI/Roster/RosterDelegate.cpp +++ b/Swift/QtUI/Roster/RosterDelegate.cpp @@ -74,9 +74,10 @@ void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& QIcon presenceIcon = index.data(PresenceIconRole).isValid() && !index.data(PresenceIconRole).value<QIcon>().isNull() ? index.data(PresenceIconRole).value<QIcon>() : QIcon(":/icons/offline.png"); + bool isIdle = index.data(IdleRole).isValid() ? index.data(IdleRole).toBool() : false; QString name = index.data(Qt::DisplayRole).toString(); QString statusText = index.data(StatusTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, 0, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, isIdle, 0, compact_); } } diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp index 1fc20dd..885d04c 100644 --- a/Swift/QtUI/Roster/RosterModel.cpp +++ b/Swift/QtUI/Roster/RosterModel.cpp @@ -84,7 +84,8 @@ QVariant RosterModel::data(const QModelIndex& index, int role) const { case AvatarRole: return getAvatar(item); case PresenceIconRole: return getPresenceIcon(item); case ChildCountRole: return getChildCount(item); - default: return QVariant(); + case IdleRole: return getIsIdle(item); + default: return QVariant(); } } @@ -93,6 +94,11 @@ int RosterModel::getChildCount(RosterItem* item) const { return group ? group->getDisplayedChildren().size() : 0; } +bool RosterModel::getIsIdle(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + return contact ? !contact->getIdleText().empty() : false; +} + QColor RosterModel::intToColor(int color) const { return QColor( ((color & 0xFF0000)>>16), @@ -131,6 +137,9 @@ QString RosterModel::getToolTip(RosterItem* item) const { if (!getStatusText(item).isEmpty()) { tip += ": " + getStatusText(item); } + if (!contact->getIdleText().empty()) { + tip += "\n " + tr("Idle since ") + P2QSTRING(contact->getIdleText()); + } } return tip; } diff --git a/Swift/QtUI/Roster/RosterModel.h b/Swift/QtUI/Roster/RosterModel.h index bd34e9c..23d54f8 100644 --- a/Swift/QtUI/Roster/RosterModel.h +++ b/Swift/QtUI/Roster/RosterModel.h @@ -18,6 +18,7 @@ namespace Swift { PresenceIconRole = Qt::UserRole + 2, StatusShowTypeRole = Qt::UserRole + 3, ChildCountRole = Qt::UserRole + 4, + IdleRole = Qt::UserRole + 5 }; class QtTreeWidget; @@ -48,6 +49,7 @@ namespace Swift { QString getStatusText(RosterItem* item) const; QIcon getPresenceIcon(RosterItem* item) const; int getChildCount(RosterItem* item) const; + bool getIsIdle(RosterItem* item) const; void reLayout(); Roster* roster_; QtTreeWidget* view_; diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index 934bd80..f1b3140 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -40,5 +40,6 @@ <file alias="emoticons/wink.png">../resources/emoticons/wink.png</file> <file alias="icons/star-checked.png">../resources/icons/star-checked2.png</file> <file alias="icons/star-unchecked.png">../resources/icons/star-unchecked2.png</file> + <file alias="icons/zzz.png">../resources/icons/zzz.png</file> </qresource> </RCC> diff --git a/Swift/resources/icons/zzz.png b/Swift/resources/icons/zzz.png Binary files differnew file mode 100644 index 0000000..706c2f4 --- /dev/null +++ b/Swift/resources/icons/zzz.png diff --git a/Swift/resources/icons/zzz.svg b/Swift/resources/icons/zzz.svg new file mode 100644 index 0000000..adbd0be --- /dev/null +++ b/Swift/resources/icons/zzz.svg @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1024" + height="1024" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.2 r9819" + version="1.0" + inkscape:export-filename="/Users/tobias/dev/rep/swift-lastseen/Swift/resources/icons/zzz.png" + inkscape:export-xdpi="2.8125" + inkscape:export-ydpi="2.8125" + sodipodi:docname="Swift_resources_icons_zzz_Before_b244d1f210fa994df40297664f111de31b6bf676.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <linearGradient + id="linearGradient3155"> + <stop + style="stop-color:#007600;stop-opacity:1;" + offset="0" + id="stop3157" /> + <stop + style="stop-color:#97f597;stop-opacity:1;" + offset="1" + id="stop3159" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective10" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3155" + id="linearGradient3185" + gradientUnits="userSpaceOnUse" + x1="146.36209" + y1="457.4635" + x2="376.76218" + y2="76.855392" + gradientTransform="matrix(0.5616546,0,0,0.5616546,113.6799,171.43482)" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.45254834" + inkscape:cx="461.16373" + inkscape:cy="517.26047" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="1440" + inkscape:window-height="852" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:snap-global="false" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid2988" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,512)"> + <path + style="fill:url(#linearGradient3185);fill-opacity:1;stroke:#004900;stroke-width:40;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:0" + d="m 400.8403,315.01501 c 0,79.25627 -64.77302,144.32061 -143.37683,143.5802 -78.9121,-0.74331 -143.37683,-64.32393 -143.37683,-143.5802 0,-80.79036 64.23282,-234.091516 143.37683,-234.091516 79.14401,0 143.37683,154.835246 143.37683,234.091516 z" + id="path2383" + sodipodi:nodetypes="csssc" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-size:145.84281920999998761px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#ffffff;font-family:Sans;stroke-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" + x="278.41916" + y="192.9375" + id="text2990" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan2992" + x="278.41916" + y="192.9375" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans;-inkscape-font-specification:Helvetica Bold"><tspan + style="font-size:290px;font-weight:bold;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;-inkscape-font-specification:Helvetica Bold;font-family:Sans;font-style:normal;font-stretch:normal;font-variant:normal" + id="tspan2998" + dy="0" + dx="0">Z</tspan><tspan + style="font-size:350px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans;-inkscape-font-specification:Helvetica Bold" + id="tspan2994" + dy="-60">Z</tspan><tspan + style="font-size:450px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans;-inkscape-font-specification:Helvetica Bold" + id="tspan2996" + dy="-80">Z</tspan></tspan></text> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="470.72134" + y="-22.449778" + id="text3000" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3002" + x="470.72134" + y="-22.449778" /></text> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="1020.8854" + y="840.59418" + id="text3791" + sodipodi:linespacing="125%" + transform="translate(0,-512)"><tspan + sodipodi:role="line" + id="tspan3793" + x="1020.8854" + y="840.59418" /></text> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="279.52814" + y="289.27185" + id="text3766" + sodipodi:linespacing="125%" + transform="translate(0,-512)"><tspan + sodipodi:role="line" + id="tspan3768" + x="279.52814" + y="289.27185" /></text> + </g> +</svg> diff --git a/Swiften/Elements/Idle.h b/Swiften/Elements/Idle.h new file mode 100644 index 0000000..572eba2 --- /dev/null +++ b/Swiften/Elements/Idle.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/posix_time_types.hpp> + +#include <Swiften/Elements/Payload.h> + +namespace Swift { + + class Idle : public Payload { + public: + typedef boost::shared_ptr<Idle> ref; + + public: + Idle() {} + Idle(boost::posix_time::ptime since) : since_(since) { + } + + void setSince(boost::posix_time::ptime since) { + since_ = since; + } + + boost::posix_time::ptime getSince() const { + return since_; + } + + private: + boost::posix_time::ptime since_; + }; + +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index a40e8f6..1797e45 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -67,6 +67,7 @@ #include <Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h> #include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h> #include <Swiften/Parser/PayloadParsers/WhiteboardParser.h> +#include <Swiften/Parser/PayloadParsers/IdleParser.h> using namespace boost; @@ -128,6 +129,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(boost::make_shared<GenericPayloadParserFactory<WhiteboardParser> >("wb", "http://swift.im/whiteboard")); factories_.push_back(boost::make_shared<DeliveryReceiptParserFactory>()); factories_.push_back(boost::make_shared<DeliveryReceiptRequestParserFactory>()); + factories_.push_back(boost::make_shared<GenericPayloadParserFactory<IdleParser> >("idle", "urn:xmpp:idle:1")); foreach(shared_ptr<PayloadParserFactory> factory, factories_) { addFactory(factory.get()); diff --git a/Swiften/Parser/PayloadParsers/IdleParser.cpp b/Swiften/Parser/PayloadParsers/IdleParser.cpp new file mode 100644 index 0000000..51aa34b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/IdleParser.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Parser/PayloadParsers/IdleParser.h> + +#include <Swiften/Base/DateTime.h> + +namespace Swift { + +IdleParser::IdleParser() : level_(0) { +} + +void IdleParser::handleStartElement(const std::string& /*element*/, const std::string& /*ns*/, const AttributeMap& attributes) { + if (level_ == 0) { + boost::posix_time::ptime since = stringToDateTime(attributes.getAttribute("since")); + getPayloadInternal()->setSince(since); + } + ++level_; +} + +void IdleParser::handleEndElement(const std::string&, const std::string&) { + --level_; +} + +void IdleParser::handleCharacterData(const std::string&) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/IdleParser.h b/Swiften/Parser/PayloadParsers/IdleParser.h new file mode 100644 index 0000000..38001b2 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/IdleParser.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/Idle.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + class IdleParser : public GenericPayloadParser<Idle> { + public: + IdleParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + int level_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/UnitTest/IdleParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/IdleParserTest.cpp new file mode 100644 index 0000000..74da474 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/IdleParserTest.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/Idle.h> +#include <Swiften/Base/DateTime.h> + +using namespace Swift; + +class IdleParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IdleParserTest); + CPPUNIT_TEST(testParse_XepWhatever_Example1); + CPPUNIT_TEST_SUITE_END(); + + public: + void testParse_XepWhatever_Example1() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<presence from='juliet@capulet.com/balcony'>\n" + "<show>away</show>\n" + "<idle xmlns='urn:xmpp:idle:1' since='1969-07-21T02:56:15Z'/>\n" + "</presence>\n" + )); + + Presence::ref presence = parser.getPayload<Presence>(); + CPPUNIT_ASSERT(presence); + Idle::ref idle = presence->getPayload<Idle>(); + CPPUNIT_ASSERT(idle); + CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == idle->getSince()); + } +}; diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 64e9eb9..068cbd7 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -69,6 +69,7 @@ sources = [ "PayloadParsers/NicknameParser.cpp", "PayloadParsers/ReplaceParser.cpp", "PayloadParsers/LastParser.cpp", + "PayloadParsers/IdleParser.cpp", "PayloadParsers/S5BProxyRequestParser.cpp", "PayloadParsers/DeliveryReceiptParser.cpp", "PayloadParsers/DeliveryReceiptRequestParser.cpp", diff --git a/Swiften/SConscript b/Swiften/SConscript index 8e6bd97..a62d344 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -375,6 +375,7 @@ if env["SCONS_STAGE"] == "build" : File("Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/MUCUserPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/IdleParserTest.cpp"), File("Parser/UnitTest/BOSHBodyExtractorTest.cpp"), File("Parser/UnitTest/AttributeMapTest.cpp"), File("Parser/UnitTest/IQParserTest.cpp"), @@ -424,6 +425,7 @@ if env["SCONS_STAGE"] == "build" : File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/IdleSerializerTest.cpp"), File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"), File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"), File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index b4822cd..3f45a7c 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -51,6 +51,7 @@ #include <Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h> #include <Swiften/Serializer/PayloadSerializers/LastSerializer.h> #include <Swiften/Serializer/PayloadSerializers/WhiteboardSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/IdleSerializer.h> #include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h> #include <Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h> @@ -110,6 +111,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new ReplaceSerializer()); serializers_.push_back(new LastSerializer()); serializers_.push_back(new WhiteboardSerializer()); + serializers_.push_back(new IdleSerializer()); serializers_.push_back(new StreamInitiationFileInfoSerializer()); serializers_.push_back(new JingleContentPayloadSerializer()); diff --git a/Swiften/Serializer/PayloadSerializers/IdleSerializer.h b/Swiften/Serializer/PayloadSerializers/IdleSerializer.h new file mode 100644 index 0000000..45f9da4 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/IdleSerializer.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/Idle.h> +#include <Swiften/Base/DateTime.h> + +namespace Swift { + class IdleSerializer : public GenericPayloadSerializer<Idle> { + public: + IdleSerializer() : GenericPayloadSerializer<Idle>() {} + + virtual std::string serializePayload(boost::shared_ptr<Idle> idle) const { + return "<idle xmlns='urn:xmpp:idle:1' since='" + dateTimeToString(idle->getSince()) + "'/>"; + } + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/IdleSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/IdleSerializerTest.cpp new file mode 100644 index 0000000..9700869 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/IdleSerializerTest.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/make_shared.hpp> + +#include <Swiften/Serializer/PayloadSerializers/IdleSerializer.h> + +using namespace Swift; + +class IdleSerializerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IdleSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + IdleSerializerTest() {} + + void testSerialize() { + IdleSerializer testling; + Idle::ref idle = boost::make_shared<Idle>(stringToDateTime("1969-07-21T02:56:15Z")); + + CPPUNIT_ASSERT_EQUAL(std::string("<idle xmlns='urn:xmpp:idle:1' since='1969-07-21T02:56:15Z'/>"), testling.serialize(idle)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IdleSerializerTest); |