dictbrowser.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*****************************************************************************
  2. * dictbrowser.cpp - QStarDict, a dictionary application for learning *
  3. * foreign languages *
  4. * Copyright (C) 2007-2023 Alexander Rodin *
  5. * *
  6. * This program is free software; you can redistribute it and/or modify *
  7. * it under the terms of the GNU General Public License as published by *
  8. * the Free Software Foundation; either version 2 of the License, or *
  9. * (at your option) any later version. *
  10. * *
  11. * This program is distributed in the hope that it will be useful, *
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  14. * GNU General Public License for more details. *
  15. * *
  16. * You should have received a copy of the GNU General Public License along *
  17. * with this program; if not, write to the Free Software Foundation, Inc., *
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
  19. *****************************************************************************/
  20. #include "dictbrowser.h"
  21. #include <QDesktopServices>
  22. #include <QFileInfo>
  23. #include <QMouseEvent>
  24. #include <QTextBlock>
  25. #include <QTextCharFormat>
  26. #include <QTextDocument>
  27. #include <QTextDocumentFragment>
  28. #include "../plugins/dictplugin.h"
  29. #include "application.h"
  30. #include "help.h"
  31. #include "ipa.h"
  32. #include "keyboard.h"
  33. #include "pluginmanager.h"
  34. #include "speaker.h"
  35. namespace
  36. {
  37. const QString translationCSS =
  38. "body {\n"
  39. #ifdef Q_OS_MAC
  40. "font-size: 13pt; font-family: Avenir; }\n"
  41. #else
  42. "font-size: 10pt; }\n"
  43. #endif
  44. "font.dict_name {\n"
  45. "color: blue;\n"
  46. "font-style: italic; }\n"
  47. "font.title {\n"
  48. "font-size: 16pt;\n"
  49. "font-weight: bold; }\n"
  50. "font.explanation {\n"
  51. "color: #7f7f7f;\n"
  52. "font-style: italic; }\n"
  53. "font.abbreviature {\n"
  54. "font-style: italic; }\n"
  55. "font.example {\n"
  56. "font-style: italic; }\n"
  57. "font.transcription {\n"
  58. "font-weight: bold; }\n";
  59. }
  60. namespace QStarDict
  61. {
  62. DictBrowser::DictBrowser(QWidget *parent)
  63. : QTextBrowser(parent),
  64. m_dict(0),
  65. m_highlighted(false),
  66. m_highlightTimerId(0),
  67. m_showLinks(true),
  68. m_showLinksModifierKey(0),
  69. m_showIpaPronouncers(false),
  70. m_highlightInCurrentTranslation(false)
  71. {
  72. document()->setDefaultStyleSheet(translationCSS);
  73. setOpenLinks(false);
  74. setOpenExternalLinks(false);
  75. connect(this, SIGNAL(anchorClicked(const QUrl &)), SLOT(on_anchorClicked(const QUrl &)));
  76. connect(this, &QTextBrowser::sourceChanged, this, &DictBrowser::on_sourceChanged);
  77. Application::instance()->installEventFilter(this);
  78. }
  79. QVariant DictBrowser::loadResource(int type, const QUrl &name)
  80. {
  81. m_highlightInCurrentTranslation = false;
  82. if (type == QTextDocument::HtmlResource && name.scheme() == "qstardict")
  83. {
  84. QString str = QUrl::fromPercentEncoding(name.toString(QUrl::RemoveScheme).toUtf8());
  85. QString result = m_dict->translate(str);
  86. if (result.isEmpty())
  87. result = "<table><tr><td><img src=\":pics/dialog-warning.png\" width=64 height=64/></td><td valign=middle>" +
  88. tr("The word <b>%1</b> is not found.").arg(str) +
  89. "</td></tr></table>";
  90. return "<title>Translation for \"" + str + "\"</title>\n"
  91. + "<body>" + result + "</body>";
  92. }
  93. else if (name.scheme() == "plugin")
  94. {
  95. DictPlugin *plugin = Application::instance()->pluginManager()->plugin<DictPlugin>(name.host());
  96. if (! plugin)
  97. return QVariant();
  98. return plugin->resource(type, name);
  99. }
  100. return QTextBrowser::loadResource(type, name);
  101. }
  102. void DictBrowser::search(const QString & exp, QTextDocument::FindFlags options)
  103. {
  104. bool found = false;
  105. QList<QTextEdit::ExtraSelection> extraSelections;
  106. moveCursor(QTextCursor::Start);
  107. QColor color = QColor(Qt::gray).lighter(130);
  108. while (find(exp, options))
  109. {
  110. found = true;
  111. QTextEdit::ExtraSelection extra;
  112. extra.format.setBackground(color);
  113. extra.cursor = textCursor();
  114. extraSelections.append(extra);
  115. }
  116. setExtraSelections(extraSelections);
  117. emit searchResult(found);
  118. }
  119. void DictBrowser::searchActive(bool active)
  120. {
  121. if (!active)
  122. {
  123. moveCursor(QTextCursor::Start);
  124. setExtraSelections({});
  125. }
  126. }
  127. void DictBrowser::invalidateHighlight()
  128. {
  129. auto overrideCursor = false;
  130. if (m_highlighted) // clear highlight if any
  131. {
  132. m_oldCursor.setCharFormat(m_oldFormat);
  133. m_highlighted = false;
  134. killTimer(m_highlightTimerId);
  135. m_highlightTimerId = 0;
  136. m_highlightedWord.clear();
  137. }
  138. if (!m_highlightInCurrentTranslation)
  139. {
  140. QApplication::restoreOverrideCursor();
  141. return;
  142. }
  143. QPoint mousePosition = mapFromGlobal(QCursor::pos());
  144. if (areLinksActive() && contentsRect().contains(mousePosition) && wordRect(mousePosition).contains(mousePosition))
  145. {
  146. // highlight word if found
  147. auto cursor = cursorForPosition(mousePosition);
  148. cursor.select(QTextCursor::WordUnderCursor);
  149. QString selection = cursor.selection().toPlainText().simplified();
  150. if (selection == m_highlightedWord || m_dict->isTranslatable(selection))
  151. {
  152. m_oldCursor = cursor;
  153. m_oldFormat = cursor.charFormat();
  154. QTextCharFormat format = m_oldFormat;
  155. format.setForeground(Qt::blue);
  156. format.setFontUnderline(true);
  157. cursor.setCharFormat(format);
  158. m_highlighted = true;
  159. m_highlightedWord = selection;
  160. m_highlightTimerId = startTimer(100);
  161. overrideCursor = true;
  162. }
  163. }
  164. if (overrideCursor)
  165. {
  166. if (!QApplication::overrideCursor())
  167. QApplication::setOverrideCursor(Qt::PointingHandCursor);
  168. }
  169. else
  170. QApplication::restoreOverrideCursor();
  171. }
  172. QRect DictBrowser::wordRect(const QPoint &mousePosition)
  173. {
  174. auto cursor = cursorForPosition(mousePosition);
  175. cursor.select(QTextCursor::WordUnderCursor);
  176. auto selectionStart = cursor.selectionStart();
  177. auto selectionEnd = cursor.selectionEnd();
  178. cursor.setPosition(selectionStart);
  179. auto topLeft = cursorRect(cursor).topLeft();
  180. cursor.setPosition(selectionEnd);
  181. auto bottomRight = cursorRect(cursor).bottomRight();
  182. return QRect(topLeft, bottomRight);
  183. }
  184. void DictBrowser::mouseMoveEvent(QMouseEvent *event)
  185. {
  186. if (areLinksActive())
  187. {
  188. m_highlightInCurrentTranslation = true;
  189. invalidateHighlight();
  190. }
  191. QTextBrowser::mouseMoveEvent(event);
  192. }
  193. bool DictBrowser::areLinksActive()
  194. {
  195. if (!m_showLinks)
  196. return false;
  197. if (m_showLinksModifierKey == 0)
  198. return true;
  199. return Keyboard::activeModifiers().testFlag(static_cast<Qt::KeyboardModifier>(m_showLinksModifierKey));
  200. }
  201. void DictBrowser::mouseReleaseEvent(QMouseEvent *event)
  202. {
  203. if (m_showIpaPronouncers)
  204. {
  205. // check whether the click was on a pronounce button
  206. QTextCursor cursor = cursorForPosition(event->pos());
  207. cursor.select(QTextCursor::WordUnderCursor);
  208. QUrl url = cursor.charFormat().anchorHref();
  209. if (url.scheme() == "pronounce")
  210. {
  211. QString ipa = QUrl::fromPercentEncoding(url.toString(QUrl::RemoveScheme).toUtf8());
  212. QString kirshenbaum = Ipa::ipaToKirshenbaum(ipa);
  213. Application::instance()->espeakSpeaker()->speak("[[" + kirshenbaum + "]]");
  214. return;
  215. }
  216. }
  217. if (areLinksActive())
  218. {
  219. QTextCursor cursor = cursorForPosition(event->pos());
  220. cursor.select(QTextCursor::WordUnderCursor);
  221. QString selection = cursor.selection().toPlainText().simplified();
  222. if (m_dict->isTranslatable(selection) &&
  223. selection != source().toString(QUrl::RemoveScheme) &&
  224. !textCursor().hasSelection())
  225. setSource(selection);
  226. }
  227. QTextBrowser::mousePressEvent(event);
  228. }
  229. void DictBrowser::on_anchorClicked(const QUrl &link)
  230. {
  231. QString scheme = link.scheme();
  232. if (scheme == "plugin" || scheme == "qrc")
  233. setSource(link);
  234. else
  235. QDesktopServices::openUrl(link);
  236. }
  237. void DictBrowser::on_sourceChanged(const QUrl &)
  238. {
  239. if (m_showIpaPronouncers)
  240. addIpaPronouncers();
  241. }
  242. void DictBrowser::timerEvent(QTimerEvent*)
  243. {
  244. invalidateHighlight();
  245. }
  246. bool DictBrowser::eventFilter(QObject *, QEvent *event)
  247. {
  248. if (event->type() == QEvent::KeyPress)
  249. {
  250. if (m_showLinksModifierKey)
  251. m_highlightInCurrentTranslation = true;
  252. invalidateHighlight();
  253. }
  254. else if (event->type() == QEvent::KeyRelease)
  255. {
  256. invalidateHighlight();
  257. }
  258. return false;
  259. }
  260. void DictBrowser::showIntro()
  261. {
  262. QString helpPath = Help::helpFilePath();
  263. QString introHtml = tr("<p align=\"center\"><a href=\"%1\">Open QStarDict documentation</a></p>").arg(helpPath);
  264. setHtml(introHtml);
  265. }
  266. void DictBrowser::addIpaPronouncers()
  267. {
  268. const static QVector<QRegularExpression> transcriptionRegExps = {
  269. Ipa::broadTranscriptionRegExp(),
  270. Ipa::narrowTranscriptionRegExp()
  271. };
  272. for (const QRegularExpression &transcriptionRegExp: transcriptionRegExps)
  273. {
  274. QTextDocument *doc = document();
  275. QTextCursor cursor;
  276. int position = 0;
  277. while (! (cursor = doc->find(transcriptionRegExp, position)).isNull())
  278. {
  279. QString transcription = cursor.selectedText();
  280. QString ipa = transcriptionRegExp.match(transcription).captured(2);
  281. cursor.insertHtml("<font class=\"transcription\">" + transcription + "</font>"
  282. "<a href=\"pronounce:" + QUrl::toPercentEncoding(ipa) + "\">"
  283. "<img style=\"vertical-align: middle\" src=\":/pics/pronounce.png\">"
  284. "</a>");
  285. position = cursor.position();
  286. }
  287. }
  288. }
  289. }
  290. // vim: tabstop=4 softtabstop=4 shiftwidth=4 expandtab cindent textwidth=120 formatoptions=tc