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