diff --git a/retroshare-gui/src/RetroShare.pro b/retroshare-gui/src/RetroShare.pro index 77f8d612f..2d94d0b22 100644 --- a/retroshare-gui/src/RetroShare.pro +++ b/retroshare-gui/src/RetroShare.pro @@ -11,7 +11,7 @@ MOC_DIR = temp/moc #CONFIG += debug debug { - QMAKE_CXXFLAGS *= -g + QMAKE_CFLAGS += -g } ################################# Linux ########################################## @@ -19,8 +19,15 @@ debug { linux-* { #CONFIG += version_detail_bash_script QMAKE_CXXFLAGS *= -D_FILE_OFFSET_BITS=64 + + system(which gpgme-config >/dev/null 2>&1) { + INCLUDEPATH += $$system(gpgme-config --cflags | sed -e "s/-I//g") + } else { + message(Could not find gpgme-config on your system, assuming gpgme.h is in /usr/include) + } + LIBS += ../../libretroshare/src/lib/libretroshare.a - LIBS += -lssl -lgpgme -lupnp -lXss + LIBS += -lssl -lgpgme -lupnp -lXss DEFINES *= HAVE_XSS # for idle time, libx screensaver extensions } @@ -208,6 +215,7 @@ HEADERS += rshare.h \ gui/profile/ProfileWidget.h \ gui/profile/StatusMessage.h \ gui/chat/PopupChatDialog.h \ + gui/chat/HandleRichText.h \ gui/channels/CreateChannel.h \ gui/channels/ChannelDetails.h \ gui/channels/CreateChannelMsg.h \ @@ -418,6 +426,7 @@ SOURCES += main.cpp \ gui/channels/CreateChannelMsg.cpp \ gui/channels/ChannelDetails.cpp \ gui/chat/PopupChatDialog.cpp \ + gui/chat/HandleRichText.cpp \ gui/connect/ConnectDialog.cpp \ gui/connect/ConfCertDialog.cpp \ gui/msgs/ChanMsgDialog.cpp \ diff --git a/retroshare-gui/src/gui/PeersDialog.cpp b/retroshare-gui/src/gui/PeersDialog.cpp index c89c06262..73682d0e6 100644 --- a/retroshare-gui/src/gui/PeersDialog.cpp +++ b/retroshare-gui/src/gui/PeersDialog.cpp @@ -998,14 +998,13 @@ void PeersDialog::insertChat() QString name = QString::fromStdString(it->name); QString line = "" + timestamp + "" + "" + " " + name + ""; + QString msgContents = QString::fromStdWString(it->msg); //std::cerr << "PeersDialog::insertChat(): 1.11\n"; - historyKeeper.addMessage(name, "THIS", QString::fromStdWString(it->msg)); + historyKeeper.addMessage(name, "THIS", msgContents); //std::cerr << "PeersDialog::insertChat(): 1.12\n"; extraTxt += line; - extraTxt += QString::fromStdWString(it->msg); - // notify with a systray icon msg if(it->rsid != rsPeers->getOwnId()) { @@ -1020,31 +1019,20 @@ void PeersDialog::insertChat() emit notifyGroupChat(QString("New group chat"), notifyMsg); } - //replace http://, https:// and www. with links - QRegExp rx("(retroshare://[^ <>]*)|(https?://[^ <>]*)|(www\\.[^ <>]*)"); - int count = 0; - int pos = 200; //ignore the first 200 char because of the standard DTD ref - while ( (pos = rx.indexIn(extraTxt, pos)) != -1 ) { - //we need to look ahead to see if it's already a well formed link - if (extraTxt.mid(pos - 6, 6) != "href=\"" && extraTxt.mid(pos - 6, 6) != "href='" && extraTxt.mid(pos - 6, 6) != "ttp://" ) { - QString tempMessg = extraTxt.left(pos) + "" + rx.cap(count) + "" + extraTxt.mid(pos + rx.matchedLength(), -1); - extraTxt = tempMessg; - } - pos += rx.matchedLength() + 15; - count ++; - } + // create a DOM tree object from the message and embed contents with HTML tags + QDomDocument doc; + doc.setContent(msgContents); - if (settings.value(QString::fromUtf8("Emoteicons_GroupChat"), true).toBool()) - { - QHashIterator i(smileys); - while(i.hasNext()) - { - i.next(); - foreach(QString code, i.key().split("|")) - extraTxt.replace(code, ""); - } - } + // embed links + QDomElement body = doc.documentElement(); + RsChat::embedHtml(doc, body, defEmbedAhref); + // embed smileys + if (settings.value(QString::fromUtf8("Emoteicons_GroupChat"), true).toBool()) + RsChat::embedHtml(doc, body, defEmbedImg); + + msgContents = doc.toString(-1); // -1 removes any annoying carriage return misinterpreted by QTextEdit + extraTxt += msgContents; if ((msgWidget->verticalScrollBar()->maximum() - 30) < msgWidget->verticalScrollBar()->value() ) { msgWidget->append(extraTxt); @@ -1357,7 +1345,7 @@ void PeersDialog::loadEmoticonsgroupchat() #endif if(!sm_file.open(QIODevice::ReadOnly)) { - std::cerr << "Could not open resouce file :/emoticons/emotes.acs" << endl ; + std::cerr << "Could not open resouce file :/emoticons/emotes.acs" << std::endl ; return ; } sm_codes = sm_file.readAll(); @@ -1407,6 +1395,9 @@ void PeersDialog::loadEmoticonsgroupchat() smileys.insert(smcode, ":/"+smfile); #endif } + + // init embedder + defEmbedImg.InitFromAwkwardHash(smileys); } void PeersDialog::smileyWidgetgroupchat() @@ -1661,13 +1652,13 @@ void PeersDialog::fileHashingFinished(AttachFileItem* file) { } //convert fileSize from uint_64 to string for html link - char fileSizeChar [100]; - sprintf(fileSizeChar, "%lld", file->FileSize()); - std::string fileSize = *(&fileSizeChar); +// char fileSizeChar [100]; +// sprintf(fileSizeChar, "%lld", file->FileSize()); +// std::string fileSize = *(&fileSizeChar); std::string mesgString = RetroShareLink(QString::fromStdString(file->FileName()), - file->FileSize(), - QString::fromStdString(file->FileHash())).toHtml().toStdString() ; + file->FileSize(), + QString::fromStdString(file->FileHash())).toHtml().toStdString() ; // std::string mesgString = "" // + "retroshare://file|" + (file->FileName()) + "|" + fileSize + "|" + (file->FileHash()) + ""; @@ -1679,13 +1670,13 @@ void PeersDialog::fileHashingFinished(AttachFileItem* file) { //convert char massageString to w_char wchar_t* message; - int requiredSize = mbstowcs(NULL, messageString, 0); // C4996 + size_t requiredSize = mbstowcs(NULL, messageString, 0); // C4996 /* Add one to leave room for the NULL terminator */ message = (wchar_t *)malloc( (requiredSize + 1) * sizeof( wchar_t )); if (! message) { std::cerr << ("Memory allocation failure.\n"); } - int size = mbstowcs( message, messageString, requiredSize + 1); // C4996 + size_t size = mbstowcs( message, messageString, requiredSize + 1); // C4996 if (size == (size_t) (-1)) { printf("Couldn't convert string--invalid multibyte character.\n"); } diff --git a/retroshare-gui/src/gui/PeersDialog.h b/retroshare-gui/src/gui/PeersDialog.h index d701ae34e..aca4dbc7c 100644 --- a/retroshare-gui/src/gui/PeersDialog.h +++ b/retroshare-gui/src/gui/PeersDialog.h @@ -25,6 +25,7 @@ #include #include "chat/PopupChatDialog.h" +#include "chat/HandleRichText.h" #include "RsAutoUpdatePage.h" #include "mainpage.h" @@ -159,8 +160,12 @@ private: ///play the sound when recv a message void playsound(); - - QString fileName; + + QString fileName; + + /** store default information for embedding HTML */ + RsChat::EmbedInHtmlAhref defEmbedAhref; + RsChat::EmbedInHtmlImg defEmbedImg; /* Worker Functions */ /* (1) Update Display */ @@ -187,7 +192,7 @@ private: QColor _currentColor; bool _underline; time_t last_status_send_time ; - + QHash smileys; std::map chatDialogs; @@ -198,5 +203,5 @@ private: Ui::PeersDialog ui; }; -#endif +#endif diff --git a/retroshare-gui/src/gui/chat/HandleRichText.cpp b/retroshare-gui/src/gui/chat/HandleRichText.cpp new file mode 100644 index 000000000..c3e5a4271 --- /dev/null +++ b/retroshare-gui/src/gui/chat/HandleRichText.cpp @@ -0,0 +1,124 @@ +/**************************************************************** + * This file is distributed under the following license: + * + * Copyright (c) 2010, Thomas Kister + * + * This program 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 "HandleRichText.h" + + +namespace RsChat { + + +void EmbedInHtmlImg::InitFromAwkwardHash(const QHash< QString, QString >& hash) +{ + QString newRE; + for(QHash::const_iterator it = hash.begin(); it != hash.end(); ++it) + foreach(QString smile, it.key().split("|")) { + smileys.insert(smile, it.value()); + newRE += "(" + QRegExp::escape(smile) + ")|"; + } + newRE.chop(1); // remove last | + myRE.setPattern(newRE); +} + + +/** + * Parses a DOM tree and replaces text by HTML tags. + * The tree is traversed depth-first, but only through children of Element type + * nodes. Any other kind of node is terminal. + * + * If the node is of type Text, its data is checked against the user-provided + * regular expression. If there is a match, the text is cut in three parts: the + * preceding part that will be inserted before, the part to be replaced, and the + * following part which will be itself checked against the regular expression. + * + * The part to be replaced is sent to a user-provided functor that will create + * the necessary embedding and return a new Element node to be inserted. + * + * @param[in] doc The whole DOM tree, necessary to create new nodes + * @param[in,out] currentElement The current node (which is of type Element) + * @param[in] embedInfos The regular expression and the type of embedding to use + */ +void embedHtml(QDomDocument& doc, QDomElement& currentElement, const EmbedInHtml& embedInfos) +{ + if(embedInfos.myRE.pattern().length() == 0) // we'll get stuck with an empty regexp + return; + + QDomNodeList children = currentElement.childNodes(); + for(uint index = 0; index < children.length(); index++) { + QDomNode tempNode = children.item(index); + if(tempNode.isElement()) { + // child is an element, we skip it if it's an tag + QDomElement tempElement = tempNode.toElement(); + if(tempElement.tagName().toLower() != "head" && tempElement.tagName().toLower() != "a") + embedHtml(doc, tempElement, embedInfos); + } + else if(tempNode.isText()) { + // child is a text, we parse it + QString tempText = tempNode.toText().data(); + if(embedInfos.myRE.indexIn(tempText) == -1) + continue; + + // there is at least one link inside, we start replacing + int currentPos = 0; + int nextPos = 0; + while((nextPos = embedInfos.myRE.indexIn(tempText, currentPos)) != -1) { + // if nextPos == 0 it means the text begins by a link + if(nextPos > 0) { + QDomText textPart = doc.createTextNode(tempText.mid(currentPos, nextPos - currentPos)); + currentElement.insertBefore(textPart, tempNode); + index++; + } + + // inserted tag + QDomElement insertedTag; + switch(embedInfos.myType) { + case Ahref: insertedTag = doc.createElement("a"); + insertedTag.setAttribute("href", embedInfos.myRE.cap(0)); + insertedTag.appendChild(doc.createTextNode(embedInfos.myRE.cap(0))); + break; + case Img: insertedTag = doc.createElement("img"); + { + const EmbedInHtmlImg& embedImg = static_cast(embedInfos); + insertedTag.setAttribute("src", embedImg.smileys[embedInfos.myRE.cap(0)]); + } + break; + } + currentElement.insertBefore(insertedTag, tempNode); + + currentPos = nextPos + embedInfos.myRE.matchedLength(); + index++; + } + + // text after the last link, only if there's one, don't touch the index + // otherwise decrement the index because we're going to remove tempNode + if(currentPos < tempText.length()) { + QDomText textPart = doc.createTextNode(tempText.mid(currentPos)); + currentElement.insertBefore(textPart, tempNode); + } + else + index--; + + currentElement.removeChild(tempNode); + } + } +} + + +} // namespace RsChat diff --git a/retroshare-gui/src/gui/chat/HandleRichText.h b/retroshare-gui/src/gui/chat/HandleRichText.h new file mode 100644 index 000000000..458a11100 --- /dev/null +++ b/retroshare-gui/src/gui/chat/HandleRichText.h @@ -0,0 +1,112 @@ +/**************************************************************** + * This file is distributed under the following license: + * + * Copyright (c) 2010, Thomas Kister + * + * This program 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. + ****************************************************************/ + +/** + * This file provides helper functions and functors for translating data from/to + * rich text format and HTML. Its main goal is to facilitate decoding of chat + * messages, particularly embedding special information into HTML tags. + */ + + +#ifndef HANDLE_RICH_TEXT_H_ +#define HANDLE_RICH_TEXT_H_ + + +#include +#include + + +namespace RsChat { + + +/** + * The type of embedding we'd like to do + */ +enum EmbeddedType +{ + Ahref, ///< into + Img, ///< into +}; + + +/** + * Base class for storing information about a given kind of embedding. + * + * Its only constructor is protected so it is impossible to instantiate it, and + * at the same time derived classes have to provide a type. + */ +class EmbedInHtml +{ +protected: + EmbedInHtml(EmbeddedType newType) : + myType(newType) + {} + +public: + const EmbeddedType myType; + QRegExp myRE; +}; + + +/** + * This class is used to store information for embedding links into tags. + */ +class EmbedInHtmlAhref : public EmbedInHtml +{ +public: + EmbedInHtmlAhref() : + EmbedInHtml(Ahref) + { + myRE.setPattern("(\\bretroshare://[^\\s]*)|(\\bhttps?://[^\\s]*)|(\\bwww\\.[^\\s]*)"); + } +}; + + +/** + * This class is used to store information for embedding smileys into tags. + * + * By default the QRegExp the variables are empty, which means it must be + * filled at runtime, typically when the smileys set is loaded. It can be + * either done by hand or by using one of the helper methods available. + * + * Note: The QHash uses only *one* smiley per key (unlike soon-to-be-upgraded + * code out there). + */ +class EmbedInHtmlImg : public EmbedInHtml +{ +public: + EmbedInHtmlImg() : + EmbedInHtml(Img) + {} + + void InitFromAwkwardHash(const QHash& hash); + + QHash smileys; +}; + + +void embedHtml(QDomDocument& doc, QDomElement& currentElement, const EmbedInHtml& embedInfos); + + +} // namespace RsChat + + +#endif // HANDLE_RICH_TEXT_H_