1 /*
2     Copyright (C) 2014 Aseman
3     http://aseman.co
4 
5     Cutegram is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     Cutegram is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "emojis.h"
20 #include "cutegram.h"
21 #include <userdata.h>
22 #include "asemantools/asemandevices.h"
23 #include "asemantools/asemantools.h"
24 
25 #define EMOJIS_PATH QString( AsemanDevices::resourcePath() + "/emojis/" )
26 #define EMOJIS_THEME_PATH(THEME) QString(EMOJIS_PATH + THEME + "/")
27 
28 #include <QHash>
29 #include <QFile>
30 #include <QDebug>
31 #include <QPointer>
32 
33 class EmojisPrivate
34 {
35 public:
36     QHash<QString,QString> emojis;
37     QStringList keys;
38     QString theme;
39     QPointer<UserData> userData;
40     QVariantMap replacements;
41 
42     QColor linkColor;
43     QColor linkVisitedColor;
44 
45     int minReplacementSize;
46     int maxReplacementSize;
47 
48     bool autoEmojis;
49 };
50 
Emojis(QObject * parent)51 Emojis::Emojis(QObject *parent) :
52     QObject(parent)
53 {
54     p = new EmojisPrivate;
55     p->maxReplacementSize = 0;
56     p->minReplacementSize = 0;
57     p->autoEmojis = false;
58 }
59 
setCurrentTheme(const QString & theme)60 void Emojis::setCurrentTheme(const QString &theme)
61 {
62     QString path = EMOJIS_THEME_PATH(theme);
63     QString conf = path + "theme";
64 
65     QFile cfile(conf);
66     if( !cfile.open(QFile::ReadOnly) )
67         return;
68 
69     p->theme = theme;
70     p->emojis.clear();
71     p->keys.clear();
72 
73     const QString data = cfile.readAll();
74     const QStringList & list = data.split("\n",QString::SkipEmptyParts);
75     foreach( const QString & l, list )
76     {
77         const QStringList & parts = l.split("\t",QString::SkipEmptyParts);
78         if( parts.count() < 2 )
79             continue;
80 
81         QString epath = parts.at(0).trimmed();
82         QString ecode = parts.at(1).trimmed();
83 
84         p->emojis[ecode] = epath;
85         p->keys << ecode;
86     }
87 
88     emit currentThemeChanged();
89 }
90 
currentTheme() const91 QString Emojis::currentTheme() const
92 {
93     return p->theme;
94 }
95 
userData() const96 UserData *Emojis::userData() const
97 {
98     return p->userData;
99 }
100 
setUserData(UserData * userData)101 void Emojis::setUserData(UserData *userData)
102 {
103     if(p->userData == userData)
104         return;
105 
106     p->userData = userData;
107     emit userDataChanged();
108 }
109 
setReplacements(const QVariantMap & map)110 void Emojis::setReplacements(const QVariantMap &map)
111 {
112     if(p->replacements == map)
113         return;
114 
115     p->replacements = map;
116     p->maxReplacementSize = 0;
117     p->minReplacementSize = 0;
118 
119     QMapIterator<QString,QVariant> i(p->replacements);
120     while(i.hasNext())
121     {
122         i.next();
123         const int length = i.key().length();
124 
125         if(!p->maxReplacementSize)
126             p->maxReplacementSize = length;
127         else
128         if(length > p->maxReplacementSize)
129             p->maxReplacementSize = length;
130 
131         if(!p->minReplacementSize)
132             p->minReplacementSize = length;
133         else
134         if(length < p->minReplacementSize)
135             p->minReplacementSize = length;
136     }
137 
138     emit replacementsChanged();
139 }
140 
replacements() const141 QVariantMap Emojis::replacements() const
142 {
143     return p->replacements;
144 }
145 
setLinkColor(const QColor & color)146 void Emojis::setLinkColor(const QColor &color)
147 {
148     if(p->linkColor == color)
149         return;
150 
151     p->linkColor = color;
152     emit linkColorChanged();
153 }
154 
linkColor() const155 QColor Emojis::linkColor() const
156 {
157     return p->linkColor;
158 }
159 
setLinkVisitedColor(const QColor & color)160 void Emojis::setLinkVisitedColor(const QColor &color)
161 {
162     if(p->linkVisitedColor == color)
163         return;
164 
165     p->linkVisitedColor = color;
166     emit linkVisitedColorChanged();
167 }
168 
linkVisitedColor() const169 QColor Emojis::linkVisitedColor() const
170 {
171     return p->linkVisitedColor;
172 }
173 
autoEmojis() const174 bool Emojis::autoEmojis() const
175 {
176     return p->autoEmojis;
177 }
178 
setAutoEmojis(bool stt)179 void Emojis::setAutoEmojis(bool stt)
180 {
181     if(p->autoEmojis == stt)
182         return;
183 
184     p->autoEmojis = stt;
185     emit autoEmojisChanged();
186 }
187 
convertSmiliesToEmoji(const QString & txt)188 QString Emojis::convertSmiliesToEmoji(const QString &txt)
189 {
190     QString res = txt;
191     for(int i=0; i<res.length()-1; i++)
192     {
193         const QChar currentString = res[i];
194         if(i!=0 && currentString != ' ' && currentString != '\n')
195             continue;
196 
197         const int smileyPointer = i==0? i : i+1;
198         for(int j=p->minReplacementSize; j<=p->maxReplacementSize; j++)
199         {
200             if(smileyPointer+j < res.length())
201             {
202                 const QChar endChar = res[smileyPointer+j];
203                 if(endChar != ' ' && endChar != '\n')
204                     continue;
205             }
206 
207             const QString &selection = res.mid(smileyPointer, j).toLower();
208             if(!p->replacements.contains(selection))
209                 continue;
210 
211             res.replace(smileyPointer, j, p->replacements.value(selection).toString());
212             i = smileyPointer;
213         }
214     }
215 
216     return res;
217 }
218 
textToEmojiText(const QString & txt,int size,bool skipLinks,bool localLinks)219 QString Emojis::textToEmojiText(const QString &txt, int size, bool skipLinks, bool localLinks)
220 {
221     QString res = p->autoEmojis? convertSmiliesToEmoji(txt) : txt;
222     res = res.toHtmlEscaped();
223 
224     QString size_folder = QString::number(size);
225     size_folder = size_folder + "x" + size_folder;
226 
227     QRegExp links_rxp("((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnrwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eouw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\\:\\d{1,5})?)(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?(?:\\b|$)");
228     int pos = 0;
229     while (!skipLinks && (pos = links_rxp.indexIn(res, pos)) != -1)
230     {
231         QString link = links_rxp.cap(0);
232         QString href = link;
233         if(href.indexOf(QRegExp("\\w+\\:\\/\\/")) == -1)
234             href = "http://" + href;
235 
236         QString atag = QString("<a href=\"%2\"><span style=\"color:%1;\">%3</span></a>").arg(p->linkColor.name(), href, link);
237         res.replace( pos, link.length(), atag );
238         pos += atag.size();
239     }
240 
241     QRegExp tags_rxp("(\\s|^)\\#(\\w+)");
242     pos = 0;
243     while (!skipLinks && (pos = tags_rxp.indexIn(res, pos)) != -1)
244     {
245         QString tag = tags_rxp.cap(2);
246         if(p->userData)
247             p->userData->addTag(tag);
248 
249         QString atag = QString("<a href='tag://%2'><span style=\"color:%1;\">%3</span></a>").arg(p->linkColor.name(), tag,"#"+tag);
250         res.replace( pos + tags_rxp.cap(1).length(), tag.length()+1, atag );
251         pos += atag.size();
252     }
253 
254     for( int i=0; i<res.size(); i++ )
255     {
256         for( int j=1; j<5; j++ )
257         {
258             QString emoji = res.mid(i,j);
259             if( !p->emojis.contains(emoji) )
260                 continue;
261 
262             QString path = EMOJIS_THEME_PATH(p->theme) + size_folder + "/" + p->emojis.value(emoji);
263             QString in_txt = QString(" <img align=absmiddle height=\"%2\" width=\"%3\" src=\"" + (localLinks?QString():AsemanDevices::localFilesPrePath()) +"%1\" /> ").arg(path).arg(size).arg(size);
264             res.replace(i,j,in_txt);
265             i += in_txt.size()-1;
266             break;
267         }
268     }
269 
270     res = res.replace("\n","<br />");
271     return res;
272 }
273 
bodyTextToEmojiText(const QString & txt)274 QString Emojis::bodyTextToEmojiText(const QString &txt)
275 {
276     QString res;
277     Qt::LayoutDirection dir = AsemanTools::directionOf(txt);
278 
279     QString dir_txt = dir==Qt::LeftToRight? "ltr" : "rtl";
280     res = QString("<html><body><p dir='%1'>").arg(dir_txt) + textToEmojiText(txt, 18) + "</p></body></html>";
281     return res;
282 }
283 
keys() const284 QList<QString> Emojis::keys() const
285 {
286     return p->keys;
287 }
288 
pathOf(const QString & key) const289 QString Emojis::pathOf(const QString &key) const
290 {
291     return EMOJIS_THEME_PATH(p->theme) + "36x36/" + p->emojis.value(key);
292 }
293 
contains(const QString & key) const294 bool Emojis::contains(const QString &key) const
295 {
296     return p->emojis.contains(key);
297 }
298 
emojis() const299 const QHash<QString, QString> &Emojis::emojis() const
300 {
301     return p->emojis;
302 }
303 
~Emojis()304 Emojis::~Emojis()
305 {
306     delete p;
307 }
308