1 /***************************************************************************
2 * *
3 * This program is free software; you can redistribute it and/or modify *
4 * it under the terms of the GNU General Public License as published by *
5 * the Free Software Foundation; either version 3 of the License, or *
6 * (at your option) any later version. *
7 * *
8 ***************************************************************************/
9
10 #include "EmoticonFactory.h"
11 #include "WulforSettings.h"
12
13 #include <QDir>
14 #include <QFile>
15 #include <QString>
16 #include <QtDebug>
17 #include <QApplication>
18 #include <QLabel>
19
20 #include <math.h>
21
22 #ifndef CLIENT_ICONS_DIR
23 #define CLIENT_ICONS_DIR ""
24 #endif
25
26 static const QString EmoticonSectionName = "emoticons-map";
27 static const QString EmoticonSubsectionName = "emoticon";
28 static const QString EmoticonTextSectionName = "name";
29
30 #if !defined (Q_WS_WIN)
31 static const QString EmotionPath = CLIENT_DATA_DIR "/emoticons/";
32 #else
33 static QString EmotionPath = "";
34 #endif
35
EmoticonFactory()36 EmoticonFactory::EmoticonFactory() :
37 QObject(NULL)
38 {
39 #if defined (Q_WS_WIN)
40 EmotionPath = qApp->applicationDirPath() + QDir::separator() + CLIENT_DATA_DIR "/emoticons/";
41 #endif
42 currentTheme = "";
43 }
44
~EmoticonFactory()45 EmoticonFactory::~EmoticonFactory(){
46 clear();
47 }
48
load()49 void EmoticonFactory::load(){
50 QString emoTheme = WSGET(WS_APP_EMOTICON_THEME, "default");
51
52 if (emoTheme.isEmpty() || (currentTheme == emoTheme))
53 return;
54
55 if (!QDir(EmotionPath+emoTheme).exists())
56 return;
57
58 QString xmlFile = EmotionPath+emoTheme+".xml";
59
60 if (!QFile::exists(xmlFile))
61 return;
62
63 QFile f(xmlFile);
64
65 if (!f.open(QIODevice::ReadOnly))
66 return;
67
68 clear();
69
70 QDomDocument dom;
71 QString err_msg = "";
72 int err_line = 0, err_col = 0;
73
74 if (dom.setContent(&f, &err_msg, &err_line, &err_col))
75 createEmoticonMap(dom);
76 else{
77 qDebug() << err_line << ":" << err_col << " " << err_msg;
78 }
79
80 f.close();
81
82 for (const auto &d : docs)
83 addEmoticons(d);
84
85 currentTheme = emoTheme;
86 }
87
addEmoticons(QTextDocument * to)88 void EmoticonFactory::addEmoticons(QTextDocument *to){
89 if (list.isEmpty() || !to)
90 return;
91
92 QString emoTheme = WSGET(WS_APP_EMOTICON_THEME);
93
94 for (const auto &i : list){
95 to->addResource( QTextDocument::ImageResource,
96 QUrl(emoTheme + "/emoticon" + QString().setNum(i->id)),
97 i->pixmap.toImage()
98 );
99 }
100
101 if (!docs.contains(to)){
102 connect(to, SIGNAL(destroyed()), this, SLOT(slotDocDeleted()));
103
104 docs << to;
105 }
106 }
107
convertEmoticons(const QString & html)108 QString EmoticonFactory::convertEmoticons(const QString &html){
109 if (html.isEmpty() || list.isEmpty() || map.isEmpty())
110 return html;
111
112 QString emoTheme = WSGET(WS_APP_EMOTICON_THEME);
113 QString out = "";
114 QString buf = html;
115
116 auto it = map.end();
117 auto begin = map.begin();
118
119 bool force_emot = WBGET(WB_APP_FORCE_EMOTICONS);
120
121 if (!force_emot){
122 buf.prepend(" ");
123 buf.append(" ");
124 }
125
126 while (!buf.isEmpty()){
127 if (buf.startsWith("<a href=") && buf.indexOf("</a>") > 0){
128 QString add = buf.left(buf.indexOf("</a>")) + "</a>";
129
130 out += add;
131 buf.remove(0, add.length());
132
133 continue;
134 }
135
136 bool found = false;
137
138 for (it = map.end()-1; it != begin-1; --it){
139 if (force_emot){
140 if (buf.startsWith(it.key())){
141 EmoticonObject *obj = it.value();
142
143 QString img = QString("<img alt=\"%1\" title=\"%1\" align=\"center\" source=\"%2/emoticon%3\" />")
144 .arg(it.key())
145 .arg(emoTheme)
146 .arg(obj->id);
147
148 out += img + " ";
149 buf.remove(0, it.key().length());
150
151 found = true;
152
153 break;
154 }
155 }
156 else{
157 if (buf.startsWith(" "+it.key()+" ")){
158 EmoticonObject *obj = it.value();
159
160 QString img = QString(" <img alt=\"%1\" title=\"%1\" align=\"center\" source=\"%2/emoticon%3\" /> ")
161 .arg(it.key())
162 .arg(emoTheme)
163 .arg(obj->id);
164
165 out += img;
166 buf.remove(0, it.key().length()+1);
167
168 found = true;
169
170 break;
171 }
172 else if (buf.startsWith(" "+it.key()+"\n")){
173 EmoticonObject *obj = it.value();
174
175 QString img = QString(" <img alt=\"%1\" title=\"%1\" align=\"center\" source=\"%2/emoticon%3\" />\n")
176 .arg(it.key())
177 .arg(emoTheme)
178 .arg(obj->id);
179
180 out += img;
181 buf.remove(0, it.key().length()+2);
182
183 found = true;
184
185 break;
186 }
187 }
188 }
189
190 if (!found){
191 out += buf.at(0);
192
193 buf.remove(0, 1);
194 }
195 }
196
197 if (!force_emot){
198 if (out.startsWith(" "))
199 out.remove(0, 1);
200 if (out.endsWith(" "))
201 out.remove(out.length()-1, 1);
202 }
203
204 return out;
205 }
206
createEmoticonMap(const QDomNode & root)207 void EmoticonFactory::createEmoticonMap(const QDomNode &root){
208 if (root.isNull())
209 return;
210
211 QDomNode r = findSectionByName(root, EmoticonSectionName);
212
213 if (r.isNull())
214 return;
215
216 DomNodeList emoNodes;
217 getSubSectionsByName(r, emoNodes, EmoticonSubsectionName);
218
219 if (emoNodes.isEmpty())
220 return;
221
222 clear();
223
224 for (const auto &node : emoNodes){
225 QString emoTheme = WSGET(WS_APP_EMOTICON_THEME);
226
227 EmoticonObject *emot = new EmoticonObject();
228 QDomElement el = node.toElement();
229
230 emot->fileName = el.attribute("file").toUtf8();
231 emot->id = list.size();
232 emot->pixmap = QPixmap();
233 emot->pixmap.load(EmotionPath+emoTheme+QDir::separator()+emot->fileName);
234
235 DomNodeList emoTexts;
236 getSubSectionsByName(node, emoTexts, EmoticonTextSectionName);
237
238 if (emoTexts.isEmpty()){
239 delete emot;
240
241 continue;
242 }
243
244 int registered = 0;
245 for (const auto &node : emoTexts){
246 QDomElement el = node.toElement();
247
248 if (el.isNull())
249 continue;
250
251 QString text = el.attribute("text").toUtf8();
252
253 if (text.isEmpty() || map.contains(text))
254 continue;
255
256 registered++;
257
258 map.insert(text, emot);
259 }
260
261 if (registered > 0)
262 list.push_back(emot);
263 else
264 delete emot;
265 }
266 }
267
fillLayout(QLayout * l,QSize & recommendedSize)268 void EmoticonFactory::fillLayout(QLayout *l, QSize &recommendedSize){
269 if (!l)
270 return;
271
272 int w = 0, h = 0, total = list.size();
273
274 if (!total){
275 recommendedSize = QSize(50, 50);
276 return;
277 }
278
279 for (const auto &i : list){
280 EmoticonLabel *lbl = new EmoticonLabel();
281
282 lbl->setPixmap(i->pixmap);
283 lbl->resize(i->pixmap.size()+QSize(2, 2));
284 lbl->setContentsMargins(1, 1, 1, 1);
285 lbl->setToolTip(map.keys(i).first());
286
287 w += lbl->width();
288 h = lbl->height();
289
290 l->addWidget(lbl);
291 }
292
293 int square = w*h;
294 int dim = static_cast<int>(sqrt(square));
295 int extra = (dim/total);//for margins
296
297 //10 extra pixels
298 recommendedSize.setHeight(dim+extra+10);
299 recommendedSize.setWidth(dim+extra+10);
300 }
301
clear()302 void EmoticonFactory::clear(){
303 qDeleteAll(list);
304
305 map.clear();
306 list.clear();
307 }
308
findSectionByName(const QDomNode & node,const QString & name)309 QDomNode EmoticonFactory::findSectionByName(const QDomNode &node, const QString &name){
310 QDomNode domNode = node.firstChild();
311
312 while (!domNode.isNull()){
313 if (domNode.isElement()){
314 QDomElement domElement = domNode.toElement();
315
316 if (!domElement.isNull() && domElement.tagName().toLower() == name.toLower())
317 return domNode;
318 }
319
320 domNode = domNode.nextSibling();
321 }
322
323 return QDomNode();
324 }
getSubSectionsByName(const QDomNode & node,EmoticonFactory::DomNodeList & list,const QString & name)325 void EmoticonFactory::getSubSectionsByName(const QDomNode &node, EmoticonFactory::DomNodeList &list, const QString &name){
326 QDomNode domNode = node.firstChild();
327
328 while (!domNode.isNull()){
329 list << domNode;
330
331 domNode = domNode.nextSibling();
332 }
333 }
334
slotDocDeleted()335 void EmoticonFactory::slotDocDeleted(){
336 QTextDocument *doc = reinterpret_cast<QTextDocument*>(sender());
337
338 if (docs.contains(doc))
339 docs.removeAt(docs.indexOf(doc));
340 }
341