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