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.pngBinary files differ new 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); | 
 Swift
 Swift