From bd4e48adb3f23f80abc8441bf359166fbe9b621c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 15 Nov 2009 14:03:05 +0100
Subject: Linkify URLs.


diff --git a/BuildTools/Coverage/.gitignore b/BuildTools/Coverage/.gitignore
new file mode 100644
index 0000000..1a06816
--- /dev/null
+++ b/BuildTools/Coverage/.gitignore
@@ -0,0 +1 @@
+results
diff --git a/QA/UnitTest/SConscript b/QA/UnitTest/SConscript
index f4bb358..2fd7ce0 100644
--- a/QA/UnitTest/SConscript
+++ b/QA/UnitTest/SConscript
@@ -7,6 +7,7 @@ if env["TEST"] :
 	myenv.MergeFlags(env["CHECKER_FLAGS"])
 	myenv.MergeFlags(env["SLIMBER_FLAGS"])
 	myenv.MergeFlags(env["SWIFT_CONTROLLERS_FLAGS"])
+	myenv.MergeFlags(env["SWIFTOOLS_FLAGS"])
 	myenv.MergeFlags(env["SWIFTEN_FLAGS"])
 	myenv.MergeFlags(env["CPPUNIT_FLAGS"])
 	myenv.MergeFlags(env["LIBIDN_FLAGS"])
diff --git a/SConstruct b/SConstruct
index 7e0f323..64f2eca 100644
--- a/SConstruct
+++ b/SConstruct
@@ -254,6 +254,10 @@ elif env.get("bonjour", False) :
 
 ################################################################################
 # Project files
+# FIXME: We need to explicitly list the order of libraries here, because of
+# the exported FLAGS. We should put FLAGS in separate SConscript files, and
+# read these in before anything else, such that we don't need to manually
+# list modules in order.
 ################################################################################
 
 # Third-party modules
@@ -266,12 +270,15 @@ SConscript(dirs = [
 # Checker
 SConscript(dirs = ["QA/Checker"])
 
-# Swiften
-SConscript(dirs = "Swiften")
+# Libraries
+SConscript(dirs = [
+		"Swiften",
+		"SwifTools"
+	])
 
 # Projects
 for dir in os.listdir(".") :
-	if dir in ["QA", "Swiften"] :
+	if dir in ["QA", "Swiften", "SwifTools"] :
 		continue 
 	sconscript = os.path.join(dir, "SConscript")
 	if os.path.isfile(sconscript) :
diff --git a/SwifTools/Linkify.cpp b/SwifTools/Linkify.cpp
new file mode 100644
index 0000000..8654307
--- /dev/null
+++ b/SwifTools/Linkify.cpp
@@ -0,0 +1,17 @@
+#include "SwifTools/Linkify.h"
+
+#include <boost/regex.hpp>
+
+namespace Swift {
+
+static const boost::regex linkifyRegexp("(https?://([-\\w\\.]+)+(:\\d+)?(/([%-\\w/_\\.]*(\\?\\S+)?)?)?)");
+
+String Linkify::linkify(const String& input) {
+	return String(boost::regex_replace(
+			input.getUTF8String(), 
+			linkifyRegexp, 
+			"<a href=\"\\1\">\\1</a>", 
+			boost::match_default|boost::format_all));
+}
+
+}
diff --git a/SwifTools/Linkify.h b/SwifTools/Linkify.h
new file mode 100644
index 0000000..04182f9
--- /dev/null
+++ b/SwifTools/Linkify.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Swiften/Base/String.h"
+
+namespace Swift {
+	namespace Linkify {
+		String linkify(const String&);
+	}
+}
diff --git a/SwifTools/SConscript b/SwifTools/SConscript
new file mode 100644
index 0000000..2caff5f
--- /dev/null
+++ b/SwifTools/SConscript
@@ -0,0 +1,15 @@
+Import("env")
+
+env["SWIFTOOLS_FLAGS"] = {
+		"LIBPATH": [Dir(".")],
+		"LIBS": ["SwifTools"]
+	}
+
+myenv = env.Clone()
+
+myenv.MergeFlags(myenv["BOOST_FLAGS"])
+myenv.StaticLibrary("SwifTools", [
+		"Linkify.cpp"
+	])
+
+SConscript(dirs = ["UnitTest"])
diff --git a/SwifTools/UnitTest/LinkifyTest.cpp b/SwifTools/UnitTest/LinkifyTest.cpp
new file mode 100644
index 0000000..9b66614
--- /dev/null
+++ b/SwifTools/UnitTest/LinkifyTest.cpp
@@ -0,0 +1,60 @@
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "SwifTools/Linkify.h"
+
+using namespace Swift;
+
+class LinkifyTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(LinkifyTest);
+		CPPUNIT_TEST(testLinkify_URLWithResource);
+		CPPUNIT_TEST(testLinkify_URLWithEmptyResource);
+		CPPUNIT_TEST(testLinkify_BareURL);
+		CPPUNIT_TEST(testLinkify_URLSurroundedByWhitespace);
+		CPPUNIT_TEST(testLinkify_MultipleURLs);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testLinkify_URLWithResource() {
+			String result = Linkify::linkify("http://swift.im/blog");
+
+			CPPUNIT_ASSERT_EQUAL(
+					String("<a href=\"http://swift.im/blog\">http://swift.im/blog</a>"),
+					result);
+		}
+
+		void testLinkify_URLWithEmptyResource() {
+			String result = Linkify::linkify("http://swift.im/");
+
+			CPPUNIT_ASSERT_EQUAL(
+					String("<a href=\"http://swift.im/\">http://swift.im/</a>"),
+					result);
+		}
+
+
+		void testLinkify_BareURL() {
+			String result = Linkify::linkify("http://swift.im");
+
+			CPPUNIT_ASSERT_EQUAL(
+					String("<a href=\"http://swift.im\">http://swift.im</a>"),
+					result);
+		}
+
+		void testLinkify_URLSurroundedByWhitespace() {
+			String result = Linkify::linkify("Foo http://swift.im/blog Bar");
+
+			CPPUNIT_ASSERT_EQUAL(
+					String("Foo <a href=\"http://swift.im/blog\">http://swift.im/blog</a> Bar"),
+					result);
+		}
+
+		void testLinkify_MultipleURLs() {
+			String result = Linkify::linkify("Foo http://swift.im/blog Bar http://el-tramo.be/about Baz");
+
+			CPPUNIT_ASSERT_EQUAL(
+					String("Foo <a href=\"http://swift.im/blog\">http://swift.im/blog</a> Bar <a href=\"http://el-tramo.be/about\">http://el-tramo.be/about</a> Baz"),
+					result);
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(LinkifyTest);
diff --git a/SwifTools/UnitTest/SConscript b/SwifTools/UnitTest/SConscript
new file mode 100644
index 0000000..2622f39
--- /dev/null
+++ b/SwifTools/UnitTest/SConscript
@@ -0,0 +1,5 @@
+Import("env")
+
+env.Append(UNITTEST_SOURCES = [
+		File("LinkifyTest.cpp")
+	])
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 0a02591..12f6beb 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -2,6 +2,7 @@
 
 #include <QtDebug>
 #include <QFile>
+#include <QDesktopServices>
 #include <QVBoxLayout>
 #include <QWebView>
 #include <QWebFrame>
@@ -18,6 +19,7 @@ QtChatView::QtChatView(QWidget* parent) : QWidget(parent) {
 	mainLayout->setContentsMargins(0,0,0,0);
 	webView_ = new QWebView(this);
 	webView_->setFocusPolicy(Qt::NoFocus);
+	connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&)));
 #ifdef Q_WS_X11
 	/* To give a border on Linux, where it looks bad without */
 	QStackedWidget* stack = new QStackedWidget(this);
@@ -88,4 +90,8 @@ void QtChatView::scrollToBottom() {
 	webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical));
 }
 
+void QtChatView::handleLinkClicked(const QUrl& url) {
+	QDesktopServices::openUrl(url);
+}
+
 }
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index 2a50129..7340e00 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -8,6 +8,7 @@
 
 class QWebView;
 class QWebPage;
+class QUrl;
 
 namespace Swift {
 	class QtChatView : public QWidget {
@@ -21,6 +22,7 @@ namespace Swift {
 		public slots:
 			void copySelectionToClipboard();
 			void scrollToBottom();
+			void handleLinkClicked(const QUrl&);
 
 		private:
 			QWebView* webView_;
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index fc8dc1e..bebebe8 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -2,6 +2,7 @@
 #include "QtSwiftUtil.h"
 #include "Roster/QtTreeWidget.h"
 #include "Roster/QtTreeWidgetFactory.h"
+#include "SwifTools/Linkify.h"
 #include "QtChatView.h"
 #include "MessageSnippet.h"
 #include "SystemMessageSnippet.h"
@@ -155,6 +156,7 @@ void QtChatWindow::addMessage(const String &message, const String &senderName, b
 	}
 	QString messageHTML(Qt::escape(P2QSTRING(message)));
 	messageHTML.replace("\n","<br/>");
+	messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML)));
 	htmlString += messageHTML;
 
 	bool appendToPrevious = !previousMessageWasSystem_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName)));
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index ee1c762..d30f3b9 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -21,6 +21,7 @@ Import("env")
 myenv = env.Clone()
 
 myenv.MergeFlags(env["SWIFT_CONTROLLERS_FLAGS"])
+myenv.MergeFlags(env["SWIFTOOLS_FLAGS"])
 myenv.MergeFlags(env["SWIFTEN_FLAGS"])
 myenv.MergeFlags(env["CPPUNIT_FLAGS"])
 myenv.MergeFlags(env["LIBIDN_FLAGS"])
-- 
cgit v0.10.2-6-g49f6