summaryrefslogtreecommitdiffstats
path: root/libkdepim/linklocator.cpp
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch)
tree67208f7c145782a7e90b123b982ca78d88cc2c87 /libkdepim/linklocator.cpp
downloadtdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz
tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libkdepim/linklocator.cpp')
-rw-r--r--libkdepim/linklocator.cpp466
1 files changed, 466 insertions, 0 deletions
diff --git a/libkdepim/linklocator.cpp b/libkdepim/linklocator.cpp
new file mode 100644
index 000000000..6058392a0
--- /dev/null
+++ b/libkdepim/linklocator.cpp
@@ -0,0 +1,466 @@
+/**
+ * linklocator.cpp
+ *
+ * Copyright (c) 2002 Dave Corrie <[email protected]>
+ *
+ * This file is part of KMail.
+ *
+ * KMail is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "linklocator.h"
+#include "pimemoticons.h"
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kstaticdeleter.h>
+#include <kmdcodec.h>
+#include <kdebug.h>
+
+#include <qstylesheet.h>
+#include <qfile.h>
+#include <qregexp.h>
+
+#include <limits.h>
+
+QMap<QString, QString> *LinkLocator::s_smileyEmoticonNameMap = 0;
+QMap<QString, QString> *LinkLocator::s_smileyEmoticonHTMLCache = 0;
+
+static KStaticDeleter< QMap<QString, QString> > smileyMapDeleter;
+static KStaticDeleter< QMap<QString, QString> > smileyCacheDeleter;
+
+LinkLocator::LinkLocator(const QString& text, int pos)
+ : mText(text), mPos(pos), mMaxUrlLen(4096), mMaxAddressLen(255)
+{
+ // If you change either of the above values for maxUrlLen or
+ // maxAddressLen, then please also update the documentation for
+ // setMaxUrlLen()/setMaxAddressLen() in the header file AND the
+ // default values used for the maxUrlLen/maxAddressLen parameters
+ // of convertToHtml().
+
+ if ( !s_smileyEmoticonNameMap ) {
+ smileyMapDeleter.setObject( s_smileyEmoticonNameMap,
+ new QMap<QString, QString>() );
+ for ( int i = 0; i < EmotIcons::EnumSindex::COUNT; ++i ) {
+ QString imageName( EmotIcons::EnumSindex::enumToString[i] );
+ imageName.truncate( imageName.length() - 2 ); //remove the _0 bit
+ s_smileyEmoticonNameMap->insert( EmotIcons::smiley(i), imageName );
+ }
+ }
+
+ if ( !s_smileyEmoticonHTMLCache )
+ smileyCacheDeleter.setObject( s_smileyEmoticonHTMLCache,
+ new QMap<QString, QString>() );
+}
+
+void LinkLocator::setMaxUrlLen(int length)
+{
+ mMaxUrlLen = length;
+}
+
+int LinkLocator::maxUrlLen() const
+{
+ return mMaxUrlLen;
+}
+
+void LinkLocator::setMaxAddressLen(int length)
+{
+ mMaxAddressLen = length;
+}
+
+int LinkLocator::maxAddressLen() const
+{
+ return mMaxAddressLen;
+}
+
+QString LinkLocator::getUrl()
+{
+ QString url;
+ if(atUrl())
+ {
+ // handle cases like this: <link>http://foobar.org/</link>
+ int start = mPos;
+ while(mPos < (int)mText.length() && mText[mPos] > ' ' && mText[mPos] != '"' &&
+ QString("<>()[]").find(mText[mPos]) == -1)
+ {
+ ++mPos;
+ }
+ /* some URLs really end with: # / & - _ */
+ const QString allowedSpecialChars = QString("#/&-_");
+ while(mPos > start && mText[mPos-1].isPunct() &&
+ allowedSpecialChars.find(mText[mPos-1]) == -1 )
+ {
+ --mPos;
+ }
+
+ url = mText.mid(start, mPos - start);
+ if(isEmptyUrl(url) || mPos - start > maxUrlLen())
+ {
+ mPos = start;
+ url = "";
+ }
+ else
+ {
+ --mPos;
+ }
+ }
+ return url;
+}
+
+// keep this in sync with KMMainWin::slotUrlClicked()
+bool LinkLocator::atUrl() const
+{
+ // the following characters are allowed in a dot-atom (RFC 2822):
+ // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
+ const QString allowedSpecialChars = QString(".!#$%&'*+-/=?^_`{|}~");
+
+ // the character directly before the URL must not be a letter, a number or
+ // any other character allowed in a dot-atom (RFC 2822).
+ if( ( mPos > 0 ) && ( mText[mPos-1].isLetterOrNumber() ||
+ ( allowedSpecialChars.find( mText[mPos-1] ) != -1 ) ) )
+ return false;
+
+ QChar ch = mText[mPos];
+ return (ch=='h' && ( mText.mid(mPos, 7) == "http://" ||
+ mText.mid(mPos, 8) == "https://") ) ||
+ (ch=='v' && mText.mid(mPos, 6) == "vnc://") ||
+ (ch=='f' && ( mText.mid(mPos, 7) == "fish://" ||
+ mText.mid(mPos, 6) == "ftp://" ||
+ mText.mid(mPos, 7) == "ftps://") ) ||
+ (ch=='s' && ( mText.mid(mPos, 7) == "sftp://" ||
+ mText.mid(mPos, 6) == "smb://") ) ||
+ (ch=='m' && mText.mid(mPos, 7) == "mailto:") ||
+ (ch=='w' && mText.mid(mPos, 4) == "www.") ||
+ (ch=='f' && mText.mid(mPos, 4) == "ftp.") ||
+ (ch=='n' && mText.mid(mPos, 5) == "news:");
+ // note: no "file:" for security reasons
+}
+
+bool LinkLocator::isEmptyUrl(const QString& url)
+{
+ return url.isEmpty() ||
+ url == "http://" ||
+ url == "https://" ||
+ url == "fish://" ||
+ url == "ftp://" ||
+ url == "ftps://" ||
+ url == "sftp://" ||
+ url == "smb://" ||
+ url == "vnc://" ||
+ url == "mailto" ||
+ url == "www" ||
+ url == "ftp" ||
+ url == "news" ||
+ url == "news://";
+}
+
+QString LinkLocator::getEmailAddress()
+{
+ QString address;
+
+ if ( mText[mPos] == '@' ) {
+ // the following characters are allowed in a dot-atom (RFC 2822):
+ // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
+ const QString allowedSpecialChars = QString(".!#$%&'*+-/=?^_`{|}~");
+
+ // determine the local part of the email address
+ int start = mPos - 1;
+ while ( start >= 0 && mText[start].unicode() < 128 &&
+ ( mText[start].isLetterOrNumber() ||
+ mText[start] == '@' || // allow @ to find invalid email addresses
+ allowedSpecialChars.find( mText[start] ) != -1 ) ) {
+ if ( mText[start] == '@' )
+ return QString(); // local part contains '@' -> no email address
+ --start;
+ }
+ ++start;
+ // we assume that an email address starts with a letter or a digit
+ while ( ( start < mPos ) && !mText[start].isLetterOrNumber() )
+ ++start;
+ if ( start == mPos )
+ return QString(); // local part is empty -> no email address
+
+ // determine the domain part of the email address
+ int dotPos = INT_MAX;
+ int end = mPos + 1;
+ while ( end < (int)mText.length() &&
+ ( mText[end].isLetterOrNumber() ||
+ mText[end] == '@' || // allow @ to find invalid email addresses
+ mText[end] == '.' ||
+ mText[end] == '-' ) ) {
+ if ( mText[end] == '@' )
+ return QString(); // domain part contains '@' -> no email address
+ if ( mText[end] == '.' )
+ dotPos = QMIN( dotPos, end ); // remember index of first dot in domain
+ ++end;
+ }
+ // we assume that an email address ends with a letter or a digit
+ while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() )
+ --end;
+ if ( end == mPos )
+ return QString(); // domain part is empty -> no email address
+ if ( dotPos >= end )
+ return QString(); // domain part doesn't contain a dot
+
+ if ( end - start > maxAddressLen() )
+ return QString(); // too long -> most likely no email address
+ address = mText.mid( start, end - start );
+
+ mPos = end - 1;
+ }
+ return address;
+}
+
+QString LinkLocator::convertToHtml(const QString& plainText, int flags,
+ int maxUrlLen, int maxAddressLen)
+{
+ LinkLocator locator(plainText);
+ locator.setMaxUrlLen(maxUrlLen);
+ locator.setMaxAddressLen(maxAddressLen);
+
+ QString str;
+ QString result((QChar*)0, (int)locator.mText.length() * 2);
+ QChar ch;
+ int x;
+ bool startOfLine = true;
+ QString emoticon;
+
+ for (locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length(); locator.mPos++, x++)
+ {
+ ch = locator.mText[locator.mPos];
+ if ( flags & PreserveSpaces )
+ {
+ if (ch==' ')
+ {
+ if (startOfLine) {
+ result += "&nbsp;";
+ locator.mPos++, x++;
+ startOfLine = false;
+ }
+ while (locator.mText[locator.mPos] == ' ')
+ {
+ result += " ";
+ locator.mPos++, x++;
+ if (locator.mText[locator.mPos] == ' ') {
+ result += "&nbsp;";
+ locator.mPos++, x++;
+ }
+ }
+ locator.mPos--, x--;
+ continue;
+ }
+ else if (ch=='\t')
+ {
+ do
+ {
+ result += "&nbsp;";
+ x++;
+ }
+ while((x&7) != 0);
+ x--;
+ startOfLine = false;
+ continue;
+ }
+ }
+ if (ch=='\n')
+ {
+ result += "<br />";
+ startOfLine = true;
+ x = -1;
+ continue;
+ }
+
+ startOfLine = false;
+ if (ch=='&')
+ result += "&amp;";
+ else if (ch=='"')
+ result += "&quot;";
+ else if (ch=='<')
+ result += "&lt;";
+ else if (ch=='>')
+ result += "&gt;";
+ else
+ {
+ const int start = locator.mPos;
+ if ( !(flags & IgnoreUrls) ) {
+ str = locator.getUrl();
+ if (!str.isEmpty())
+ {
+ QString hyperlink;
+ if(str.left(4) == "www.")
+ hyperlink = "http://" + str;
+ else if(str.left(4) == "ftp.")
+ hyperlink = "ftp://" + str;
+ else
+ hyperlink = str;
+
+ str = str.replace('&', "&amp;");
+ result += "<a href=\"" + hyperlink + "\">" + str + "</a>";
+ x += locator.mPos - start;
+ continue;
+ }
+ str = locator.getEmailAddress();
+ if(!str.isEmpty())
+ {
+ // len is the length of the local part
+ int len = str.find('@');
+ QString localPart = str.left(len);
+
+ // remove the local part from the result (as '&'s have been expanded to
+ // &amp; we have to take care of the 4 additional characters per '&')
+ result.truncate(result.length() - len - (localPart.contains('&')*4));
+ x -= len;
+
+ result += "<a href=\"mailto:" + str + "\">" + str + "</a>";
+ x += str.length() - 1;
+ continue;
+ }
+ }
+ if ( flags & ReplaceSmileys ) {
+ str = locator.getEmoticon();
+ if ( ! str.isEmpty() ) {
+ result += str;
+ x += locator.mPos - start;
+ continue;
+ }
+ }
+ if ( flags & HighlightText ) {
+ str = locator.highlightedText();
+ if ( !str.isEmpty() ) {
+ result += str;
+ x += locator.mPos - start;
+ continue;
+ }
+ }
+ result += ch;
+ }
+ }
+
+ return result;
+}
+
+QString LinkLocator::pngToDataUrl( const QString & iconPath )
+{
+ if ( iconPath.isEmpty() )
+ return QString::null;
+
+ QFile pngFile( iconPath );
+ if ( !pngFile.open( IO_ReadOnly | IO_Raw ) )
+ return QString::null;
+
+ QByteArray ba = pngFile.readAll();
+ pngFile.close();
+ return QString::fromLatin1("data:image/png;base64,%1")
+ .arg( KCodecs::base64Encode( ba ) );
+}
+
+
+QString LinkLocator::getEmoticon()
+{
+ // smileys have to be prepended by whitespace
+ if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() )
+ return QString::null;
+
+ // since smileys start with ':', ';', '(' or '8' short circuit method
+ const QChar ch = mText[mPos];
+ if ( ch !=':' && ch != ';' && ch != '(' && ch != '8' )
+ return QString::null;
+
+ // find the end of the smiley (a smiley is at most 4 chars long and ends at
+ // lineend or whitespace)
+ const int MinSmileyLen = 2;
+ const int MaxSmileyLen = 4;
+ int smileyLen = 1;
+ while ( ( smileyLen <= MaxSmileyLen ) &&
+ ( mPos+smileyLen < (int)mText.length() ) &&
+ !mText[mPos+smileyLen].isSpace() )
+ smileyLen++;
+ if ( smileyLen < MinSmileyLen || smileyLen > MaxSmileyLen )
+ return QString::null;
+
+ const QString smiley = mText.mid( mPos, smileyLen );
+ if ( !s_smileyEmoticonNameMap->contains( smiley ) )
+ return QString::null; // that's not a (known) smiley
+
+ QString htmlRep;
+ if ( s_smileyEmoticonHTMLCache->contains( smiley ) ) {
+ htmlRep = (*s_smileyEmoticonHTMLCache)[smiley];
+ }
+ else {
+ const QString imageName = (*s_smileyEmoticonNameMap)[smiley];
+
+#if KDE_IS_VERSION( 3, 3, 91 )
+ const QString iconPath = locate( "emoticons",
+ EmotIcons::theme() +
+ QString::fromLatin1( "/" ) +
+ imageName + QString::fromLatin1(".png") );
+#else
+ const QString iconPath = locate( "data",
+ QString::fromLatin1( "kopete/pics/emoticons/" )+
+ EmotIcons::theme() +
+ QString::fromLatin1( "/" ) +
+ imageName + QString::fromLatin1(".png") );
+#endif
+
+ const QString dataUrl = pngToDataUrl( iconPath );
+ if ( dataUrl.isEmpty() ) {
+ htmlRep = QString::null;
+ }
+ else {
+ // create an image tag (the text in attribute alt is used
+ // for copy & paste) representing the smiley
+ htmlRep = QString("<img class=\"pimsmileyimg\" src=\"%1\" "
+ "alt=\"%2\" title=\"%3\" width=\"16\" height=\"16\"/>")
+ .arg( dataUrl,
+ QStyleSheet::escape( smiley ),
+ QStyleSheet::escape( smiley ) );
+ }
+ s_smileyEmoticonHTMLCache->insert( smiley, htmlRep );
+ }
+
+ if ( !htmlRep.isEmpty() )
+ mPos += smileyLen - 1;
+
+ return htmlRep;
+}
+
+QString LinkLocator::highlightedText()
+{
+ // formating symbols must be prepended with a whitespace
+ if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() )
+ return QString::null;
+
+ const QChar ch = mText[mPos];
+ if ( ch != '/' && ch != '*' && ch != '_' )
+ return QString::null;
+
+ QRegExp re = QRegExp( QString("\\%1([0-9A-Za-z]+)\\%2").arg( ch ).arg( ch ) );
+ if ( re.search( mText, mPos ) == mPos ) {
+ uint length = re.matchedLength();
+ // there must be a whitespace after the closing formating symbol
+ if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() )
+ return QString::null;
+ mPos += length - 1;
+ switch ( ch.latin1() ) {
+ case '*':
+ return "<b>" + re.cap( 1 ) + "</b>";
+ case '_':
+ return "<u>" + re.cap( 1 ) + "</u>";
+ case '/':
+ return "<i>" + re.cap( 1 ) + "</i>";
+ }
+ }
+ return QString::null;
+}
+