path fixed
[qstardict] / plugins / stardict / stardict.cpp
1 /*****************************************************************************
2  * stardict.cpp - QStarDict, a StarDict clone written using Qt               *
3  * Copyright (C) 2008 Alexander Rodin                                        *
4  *                                                                           *
5  * This program 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 2 of the License, or         *
8  * (at your option) any later version.                                       *
9  *                                                                           *
10  * This program 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 along   *
16  * with this program; if not, write to the Free Software Foundation, Inc.,   *
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.               *
18  *****************************************************************************/
19
20 #include "stardict.h"
21
22 #include <list>
23 #include <map>
24 #include <string>
25 #include <utility>
26 #include <QCoreApplication>
27 #include <QDir>
28 #include <QFile>
29 #include <QSettings>
30 #include <QStack>
31 #include <glib/gmem.h>
32 #include <glib/gstrfuncs.h>
33 #include "lib.h"
34 #include "file.hpp"
35 #include "settingsdialog.h"
36 #include <QDebug>
37 namespace
38 {
39 void xdxf2html(QString &str);
40 QString whereDict(const QString &name, const QStringList &dictDirs);
41 const int MaxFuzzy = 24;
42
43 class StdList: public std::list<std::string>
44 {
45     public:
46         StdList()
47             : std::list<std::string>()
48         { }
49
50         StdList(const QList<QString> &list)
51             : std::list<std::string>()
52         {
53             for (QList<QString>::const_iterator i = list.begin(); i != list.end(); ++i)
54                 push_back(i->toUtf8().data());
55         }
56
57         StdList(const std::list<std::string> &list)
58             : std::list<std::string>(list)
59         { }
60
61         QStringList toStringList() const
62         {
63             QStringList list;
64             for (const_iterator i = begin(); i != end(); ++i)
65                 list << QString::fromUtf8(i->c_str());
66             return list;
67         }
68 };
69
70 class IfoListSetter
71 {
72     public:
73         IfoListSetter(QStringList *list)
74             : m_list(list)
75         { }
76
77         void operator ()(const std::string &filename, bool)
78         {
79             DictInfo info;
80             if (info.load_from_ifo_file(filename, false))
81                 m_list->push_back(QString::fromUtf8(info.bookname.c_str()));
82         }
83
84     private:
85         QStringList *m_list;
86 };
87
88 class IfoFileFinder
89 {
90     public:
91         IfoFileFinder(const QString &name, QString *filename)
92             : m_name(name.toUtf8().data()),
93               m_filename(filename)
94         { }
95
96         void operator()(const std::string &filename, bool)
97         {
98             DictInfo info;
99             if (info.load_from_ifo_file(filename, false) && info.bookname == m_name) {
100                 *m_filename = QString::fromUtf8(filename.c_str());
101             }
102         }
103
104     private:
105         std::string m_name;
106         QString *m_filename;
107 };
108 }
109
110 StarDict::StarDict(QObject *parent)
111     : QObject(parent)
112 {
113     m_sdLibs = new Libs;
114     QSettings settings("qstardict","qstardict");
115
116     m_dictDirs = settings.value("StarDict/dictDirs", m_dictDirs).toStringList();
117     m_reformatLists = settings.value("StarDict/reformatLists", true).toBool();
118     m_expandAbbreviations = settings.value("StarDict/expandAbbreviations", true).toBool();
119     if (m_dictDirs.isEmpty())
120     {
121 #ifdef Q_OS_UNIX
122         m_dictDirs << "/usr/share/stardict/dic";
123 #else
124         m_dictDirs << QCoreApplication::applicationDirPath() + "/dic";
125 #endif // Q_OS_UNIX                             
126         m_dictDirs << QDir::homePath() + "/.stardict/dic";
127         m_dictDirs << QDir::homePath() + "/MyDocs/stardict/dic";
128     }
129 }
130
131 StarDict::~StarDict()
132 {
133     QSettings settings("qstardict","qstardict");
134     settings.setValue("StarDict/dictDirs", m_dictDirs);
135     settings.setValue("StarDict/reformatLists", m_reformatLists);
136     settings.setValue("StarDict/expandAbbreviations", m_expandAbbreviations);
137     delete m_sdLibs;
138 }
139
140 QStringList StarDict::availableDicts() const
141 {
142     QStringList result;
143     IfoListSetter setter(&result);
144     for_each_file(StdList(m_dictDirs), ".ifo", StdList(), StdList(), setter);
145
146     return result;
147 }
148
149 void StarDict::setLoadedDicts(const QStringList &loadedDicts)
150 {
151     QStringList available = availableDicts();
152     StdList disabled;
153     for (QStringList::const_iterator i = available.begin(); i != available.end(); ++i)
154     {
155         if (! loadedDicts.contains(*i))
156             disabled.push_back(i->toUtf8().data());
157     }
158     m_sdLibs->reload(StdList(m_dictDirs), StdList(loadedDicts), disabled);
159
160     m_loadedDicts.clear();
161     for (int i = 0; i < m_sdLibs->ndicts(); ++i)
162         m_loadedDicts[QString::fromUtf8(m_sdLibs->dict_name(i).c_str())] = i;
163 }
164
165 StarDict::DictInfo StarDict::dictInfo(const QString &dict)
166 {
167     ::DictInfo nativeInfo;
168     nativeInfo.wordcount = 0;
169     if (! nativeInfo.load_from_ifo_file(whereDict(dict, m_dictDirs).toUtf8().data(), false)) {
170         return DictInfo();
171     }
172     DictInfo result(name(), dict);
173     result.setAuthor(QString::fromUtf8(nativeInfo.author.c_str()));
174     result.setDescription(QString::fromUtf8(nativeInfo.description.c_str()));
175     result.setWordsCount(nativeInfo.wordcount ? static_cast<long>(nativeInfo.wordcount) : -1);
176     return result;
177 }
178
179 bool StarDict::isTranslatable(const QString &dict, const QString &word)
180 {
181     if (! m_loadedDicts.contains(dict))
182         return false;
183     long ind;
184     return m_sdLibs->SimpleLookupWord(word.toUtf8().data(), ind, m_loadedDicts[dict]);
185 }
186
187 StarDict::Translation StarDict::translate(const QString &dict, const QString &word)
188 {
189     if (! m_loadedDicts.contains(dict))
190         return Translation();
191     if (word.isEmpty())
192         return Translation();
193     int dictIndex = m_loadedDicts[dict];
194     long ind;
195     if (! m_sdLibs->SimpleLookupWord(word.toUtf8().data(), ind, m_loadedDicts[dict]))
196         return Translation();
197     return Translation(QString::fromUtf8(m_sdLibs->poGetWord(ind, dictIndex)),
198             QString::fromUtf8(m_sdLibs->dict_name(dictIndex).c_str()),
199             parseData(m_sdLibs->poGetWordData(ind, dictIndex), dictIndex, true,
200                 m_reformatLists, m_expandAbbreviations));
201 }
202
203 QStringList StarDict::findSimilarWords(const QString &dict, const QString &word)
204 {
205     if (! m_loadedDicts.contains(dict))
206         return QStringList();
207     gchar *fuzzy_res[MaxFuzzy];
208     if (! m_sdLibs->LookupWithFuzzy(word.toUtf8().data(), fuzzy_res, MaxFuzzy, m_loadedDicts[dict]))
209         return QStringList();
210     QStringList result;
211     for (gchar **p = fuzzy_res, **end = fuzzy_res + MaxFuzzy; p != end && *p; ++p)
212     {
213         result << QString::fromUtf8(*p);
214         g_free(*p);
215     }
216     return result;
217 }
218
219 int StarDict::execSettingsDialog(QWidget *parent)
220 {
221     ::SettingsDialog dialog(this, parent);
222     return dialog.exec();
223 }
224
225 QString StarDict::parseData(const char *data, int dictIndex, bool htmlSpaces, bool reformatLists, bool expandAbbreviations)
226 {
227     QString result;
228     quint32 dataSize = *reinterpret_cast<const quint32*>(data);
229     const char *dataEnd = data + dataSize;
230     const char *ptr = data + sizeof(quint32);
231     while (ptr < dataEnd)
232     {
233         switch (*ptr++)
234         {
235             case 'm':
236             case 'l':
237             case 'g':
238             {
239                 QString str = QString::fromUtf8(ptr);
240                 ptr += str.toUtf8().length() + 1;
241                 result += str;
242                 break;
243             }
244             case 'x':
245             {
246                 QString str = QString::fromUtf8(ptr);
247                 ptr += str.toUtf8().length() + 1;
248                 xdxf2html(str);
249                 result += str;
250                 break;
251             }
252             case 't':
253             {
254                 QString str = QString::fromUtf8(ptr);
255                 ptr += str.toUtf8().length() + 1;
256                 result += "<font class=\"example\">";
257                 result += str;
258                 result += "</font>";
259                 break;
260             }
261             case 'y':
262             {
263                 ptr += strlen(ptr) + 1;
264                 break;
265             }
266             case 'W':
267             case 'P':
268             {
269                 ptr += *reinterpret_cast<const quint32*>(ptr) + sizeof(quint32);
270                 break;
271             }
272             default:
273                 ; // nothing
274         }
275     }
276
277     if (expandAbbreviations)
278     {
279         QRegExp regExp("_\\S+[\\.:]");
280         int pos = 0;
281         while ((pos = regExp.indexIn(result, pos)) != -1)
282         {
283             long ind;
284             if (m_sdLibs->SimpleLookupWord(result.mid(pos, regExp.matchedLength()).toUtf8().data(), ind, dictIndex))
285             {
286                 QString expanded = "<font class=\"explanation\">";
287                 expanded += parseData(m_sdLibs->poGetWordData(ind, dictIndex));
288                 if (result[pos + regExp.matchedLength() - 1] == ':')
289                     expanded += ':';
290                 expanded += "</font>";
291                 result.replace(pos, regExp.matchedLength(), expanded);
292                 pos += expanded.length();
293             }
294             else
295                 pos += regExp.matchedLength();
296         }
297     }
298     if (reformatLists)
299     {
300         int pos = 0;
301         QStack<QChar> openedLists;
302         while (pos < result.length())
303         {
304             if (result[pos].isDigit())
305             {
306                 int n = 0;
307                 while (result[pos + n].isDigit())
308                     ++n;
309                 pos += n;
310                 if (result[pos] == '&' && result.mid(pos + 1, 3) == "gt;")
311                     result.replace(pos, 4, ">");
312                 QChar marker = result[pos];
313                 QString replacement;
314                 if (marker == '>' || marker == '.' || marker == ')')
315                 {
316                     if (n == 1 && result[pos - 1] == '1') // open new list
317                     {
318                         if (openedLists.contains(marker))
319                         {
320                             replacement = "</li></ol>";
321                             while (openedLists.size() && openedLists.top() != marker)
322                             {
323                                 replacement += "</li></ol>";
324                                 openedLists.pop();
325                             }
326                         }
327                         openedLists.push(marker);
328                         replacement += "<ol>";
329                     }
330                     else
331                     {
332                         while (openedLists.size() && openedLists.top() != marker)
333                         {
334                             replacement += "</li></ol>";
335                             openedLists.pop();
336                         }
337                         replacement += "</li>";
338                     }
339                     replacement += "<li>";
340                     pos -= n;
341                     n += pos;
342                     while (result[pos - 1].isSpace())
343                         --pos;
344                     while (result[n + 1].isSpace())
345                         ++n;
346                     result.replace(pos, n - pos + 1, replacement);
347                     pos += replacement.length();
348                 }
349                 else
350                     ++pos;
351             }
352             else
353                 ++pos;
354         }
355         while (openedLists.size())
356         {
357             result += "</li></ol>";
358             openedLists.pop();
359         }
360     }
361     if (htmlSpaces)
362     {
363         int n = 0;
364         while (result[n].isSpace())
365             ++n;
366         result.remove(0, n);
367         n = 0;
368         while (result[result.length() - 1 - n].isSpace())
369             ++n;
370         result.remove(result.length() - n, n);
371
372         for (int pos = 0; pos < result.length();)
373         {
374             switch (result[pos].toAscii())
375             {
376                 case '[':
377                     result.insert(pos, "<font class=\"transcription\">");
378                     pos += 28 + 1; // sizeof "<font class=\"transcription\">" + 1
379                     break;
380                 case ']':
381                     result.insert(pos + 1, "</font>");
382                     pos += 7 + 1; // sizeof "</font>" + 1
383                     break;
384                 case '\t':
385                     result.insert(pos, "&nbsp;&nbsp;&nbsp;&nbsp;");
386                     pos += 24 + 1; // sizeof "&nbsp;&nbsp;&nbsp;&nbsp;" + 1
387                     break;
388                 case '\n':
389                 {
390                     int count = 1;
391                     n = 1;
392                     while (result[pos + n].isSpace())
393                     {
394                         if (result[pos + n] == '\n')
395                             ++count;
396                         ++n;
397                     }
398                     if (count > 1)
399                         result.replace(pos, n, "</p><p>");
400                     else
401                         result.replace(pos, n, "<br>");
402                     break;
403                 }
404                 default:
405                     ++pos;
406             }
407         }
408     }
409     return result;
410 }
411
412 namespace
413 {
414 QString whereDict(const QString &name, const QStringList &dictDirs)
415 {
416     QString filename;
417     IfoFileFinder finder(name, &filename);
418     for_each_file(StdList(dictDirs), ".ifo", StdList(), StdList(), finder);
419     return filename;
420 }
421
422 void xdxf2html(QString &str)
423 {
424     str.replace("<abr>", "<font class=\"abbreviature\">");
425     str.replace("<tr>", "<font class=\"transcription\">[");
426     str.replace("</tr>", "]</font>");
427     str.replace("<ex>", "<font class=\"example\">");
428     str.replace(QRegExp("<k>.*<\\/k>"), "");
429     str.replace(QRegExp("(<\\/abr>)|(<\\ex>)"), "</font");
430 }
431
432 }
433
434 Q_EXPORT_PLUGIN2(stardict, StarDict)
435
436 // vim: tabstop=4 softtabstop=4 shiftwidth=4 expandtab cindent textwidth=120 formatoptions=tc