1 /* Copyright (C) 2012 J.F.Dockes
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the
14 * Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 */
17 #include "autoconfig.h"
18
19 #include <stdio.h>
20
21 #include <string>
22 #include <vector>
23 #include <sstream>
24
25 #if defined(USING_WEBKIT)
26 # include <QWebSettings>
27 # include <QWebFrame>
28 # include <QUrl>
29 # define QWEBSETTINGS QWebSettings
30 # define QWEBPAGE QWebPage
31 #elif defined(USING_WEBENGINE)
32 // Notes for WebEngine
33 // - All links must begin with http:// for acceptNavigationRequest to be
34 // called.
35 // - The links passed to acceptNav.. have the host part
36 // lowercased -> we change S0 to http://h/S0, not http://S0
37 # include <QWebEnginePage>
38 # include <QWebEngineSettings>
39 # include <QtWebEngineWidgets>
40 # define QWEBSETTINGS QWebEngineSettings
41 # define QWEBPAGE QWebEnginePage
42 #else
43 #include <QTextBrowser>
44 #endif
45
46 #include <QShortcut>
47
48 #include "log.h"
49 #include "recoll.h"
50 #include "snippets_w.h"
51 #include "guiutils.h"
52 #include "rcldb.h"
53 #include "rclhelp.h"
54 #include "plaintorich.h"
55 #include "scbase.h"
56
57 using namespace std;
58
59 #if defined(USING_WEBKIT)
60 #define browser ((QWebView*)browserw)
61 #elif defined(USING_WEBENGINE)
62 #define browser ((QWebEngineView*)browserw)
63 #else
64 #define browser ((QTextBrowser*)browserw)
65 #endif
66
67 class PlainToRichQtSnippets : public PlainToRich {
68 public:
startMatch(unsigned int)69 virtual string startMatch(unsigned int) {
70 return string("<span class='rclmatch' style='")
71 + qs2utf8s(prefs.qtermstyle) + string("'>");
72 }
endMatch()73 virtual string endMatch() {
74 return string("</span>");
75 }
76 };
77 static PlainToRichQtSnippets g_hiliter;
78
init()79 void SnippetsW::init()
80 {
81 m_sortingByPage = prefs.snipwSortByPage;
82 QPushButton *searchButton = new QPushButton(tr("Search"));
83 searchButton->setAutoDefault(false);
84 buttonBox->addButton(searchButton, QDialogButtonBox::ActionRole);
85 // setWindowFlags(Qt::WindowStaysOnTopHint);
86 searchFM->hide();
87
88 onNewShortcuts();
89 connect(&SCBase::scBase(), SIGNAL(shortcutsChanged()),
90 this, SLOT(onNewShortcuts()));
91
92 QPushButton *closeButton = buttonBox->button(QDialogButtonBox::Close);
93 if (closeButton)
94 connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
95 connect(searchButton, SIGNAL(clicked()), this, SLOT(slotEditFind()));
96 connect(searchLE, SIGNAL(textChanged(const QString&)),
97 this, SLOT(slotSearchTextChanged(const QString&)));
98 connect(nextPB, SIGNAL(clicked()), this, SLOT(slotEditFindNext()));
99 connect(prevPB, SIGNAL(clicked()), this, SLOT(slotEditFindPrevious()));
100
101
102 // Get rid of the placeholder widget created from the .ui
103 delete browserw;
104 #if defined(USING_WEBKIT)
105 browserw = new QWebView(this);
106 verticalLayout->insertWidget(0, browserw);
107 browser->setUrl(QUrl(QString::fromUtf8("about:blank")));
108 connect(browser, SIGNAL(linkClicked(const QUrl &)),
109 this, SLOT(onLinkClicked(const QUrl &)));
110 browser->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
111 browser->page()->currentFrame()->setScrollBarPolicy(Qt::Horizontal,
112 Qt::ScrollBarAlwaysOff);
113 QWEBSETTINGS *ws = browser->page()->settings();
114 if (prefs.reslistfontfamily != "") {
115 ws->setFontFamily(QWEBSETTINGS::StandardFont, prefs.reslistfontfamily);
116 ws->setFontSize(QWEBSETTINGS::DefaultFontSize, prefs.reslistfontsize);
117 }
118 if (!prefs.snipCssFile.isEmpty())
119 ws->setUserStyleSheetUrl(QUrl::fromLocalFile(prefs.snipCssFile));
120 browserw->setContextMenuPolicy(Qt::CustomContextMenu);
121 connect(browserw, SIGNAL(customContextMenuRequested(const QPoint&)),
122 this, SLOT(createPopupMenu(const QPoint&)));
123 #elif defined(USING_WEBENGINE)
124 browserw = new QWebEngineView(this);
125 verticalLayout->insertWidget(0, browserw);
126 browser->setPage(new SnipWebPage(this));
127 QWEBSETTINGS *ws = browser->page()->settings();
128 if (prefs.reslistfontfamily != "") {
129 ws->setFontFamily(QWEBSETTINGS::StandardFont, prefs.reslistfontfamily);
130 ws->setFontSize(QWEBSETTINGS::DefaultFontSize, prefs.reslistfontsize);
131 }
132 // Stylesheet TBD
133 browserw->setContextMenuPolicy(Qt::CustomContextMenu);
134 connect(browserw, SIGNAL(customContextMenuRequested(const QPoint&)),
135 this, SLOT(createPopupMenu(const QPoint&)));
136 #else
137 browserw = new QTextBrowser(this);
138 verticalLayout->insertWidget(0, browserw);
139 connect(browser, SIGNAL(anchorClicked(const QUrl &)),
140 this, SLOT(onLinkClicked(const QUrl &)));
141 browser->setReadOnly(true);
142 browser->setUndoRedoEnabled(false);
143 browser->setOpenLinks(false);
144 browser->setTabChangesFocus(true);
145 if (prefs.reslistfontfamily != "") {
146 QFont nfont(prefs.reslistfontfamily, prefs.reslistfontsize);
147 browser->setFont(nfont);
148 } else {
149 browser->setFont(QFont());
150 }
151 #endif
152 }
153
onNewShortcuts()154 void SnippetsW::onNewShortcuts()
155 {
156 SETSHORTCUT(this, "snippets:156", tr("Snippets Window"), tr("Find"),
157 "Ctrl+F", m_find1sc, slotEditFind);
158 SETSHORTCUT(this, "snippets:158", tr("Snippets Window"), tr("Find (alt)"),
159 "/", m_find2sc, slotEditFind);
160 SETSHORTCUT(this, "snippets:160", tr("Snippets Window"), tr("Find next"),
161 "F3", m_findnextsc, slotEditFindNext);
162 SETSHORTCUT(this, "snippets:162", tr("Snippets Window"), tr("Find previous"),
163 "Shift+F3", m_findprevsc, slotEditFindPrevious);
164 SETSHORTCUT(this, "snippets:164", tr("Snippets Window"), tr("Close window"),
165 "Esc", m_hidesc, hide);
166 }
167
listShortcuts()168 void SnippetsW::listShortcuts()
169 {
170 LISTSHORTCUT(this, "snippets:156", tr("Snippets Window"), tr("Find"),
171 "Ctrl+F", m_find1sc, slotEditFind);
172 LISTSHORTCUT(this, "snippets:158", tr("Snippets Window"), tr("Find (alt)"),
173 "/", m_find2sc, slotEditFind);
174 LISTSHORTCUT(this, "snippets:160",tr("Snippets Window"), tr("Find next"),
175 "F3", m_find2sc, slotEditFindNext);
176 LISTSHORTCUT(this, "snippets:162",tr("Snippets Window"), tr("Find previous"),
177 "Shift+F3", m_find2sc, slotEditFindPrevious);
178 LISTSHORTCUT(this, "snippets:164", tr("Snippets Window"), tr("Close window"),
179 "Esc", m_hidesc, hide);
180 }
181
createPopupMenu(const QPoint & pos)182 void SnippetsW::createPopupMenu(const QPoint& pos)
183 {
184 QMenu *popup = new QMenu(this);
185 if (m_sortingByPage) {
186 popup->addAction(tr("Sort By Relevance"), this,
187 SLOT(reloadByRelevance()));
188 } else {
189 popup->addAction(tr("Sort By Page"), this, SLOT(reloadByPage()));
190 }
191 popup->popup(mapToGlobal(pos));
192 }
193
reloadByRelevance()194 void SnippetsW::reloadByRelevance()
195 {
196 m_sortingByPage = false;
197 onSetDoc(m_doc, m_source);
198 }
reloadByPage()199 void SnippetsW::reloadByPage()
200 {
201 m_sortingByPage = true;
202 onSetDoc(m_doc, m_source);
203 }
204
onSetDoc(Rcl::Doc doc,std::shared_ptr<DocSequence> source)205 void SnippetsW::onSetDoc(Rcl::Doc doc, std::shared_ptr<DocSequence> source)
206 {
207 m_doc = doc;
208 m_source = source;
209 if (!source)
210 return;
211
212 // Make title out of file name if none yet
213 string titleOrFilename;
214 string utf8fn;
215 m_doc.getmeta(Rcl::Doc::keytt, &titleOrFilename);
216 m_doc.getmeta(Rcl::Doc::keyfn, &utf8fn);
217 if (titleOrFilename.empty()) {
218 titleOrFilename = utf8fn;
219 }
220 QString title("Recoll - Snippets");
221 if (!titleOrFilename.empty()) {
222 title += QString(" : ") + QString::fromUtf8(titleOrFilename.c_str());
223 }
224 setWindowTitle(title);
225
226 vector<Rcl::Snippet> vpabs;
227 source->getAbstract(m_doc, vpabs, prefs.snipwMaxLength, m_sortingByPage);
228
229 HighlightData hdata;
230 source->getTerms(hdata);
231
232 ostringstream oss;
233 oss <<
234 "<html><head>"
235 "<meta http-equiv=\"content-type\" "
236 "content=\"text/html; charset=utf-8\">";
237
238 oss << "<style type=\"text/css\">\nbody,table,select,input {\n";
239 oss << "color: " + qs2utf8s(prefs.fontcolor) + ";\n";
240 oss << "}\n</style>\n";
241 oss << qs2utf8s(prefs.darkreslistheadertext) << qs2utf8s(prefs.reslistheadertext);
242
243 oss <<
244 "</head>"
245 "<body>"
246 "<table class=\"snippets\">"
247 ;
248
249 g_hiliter.set_inputhtml(false);
250 bool nomatch = true;
251
252 for (const auto& snippet : vpabs) {
253 if (snippet.page == -1) {
254 oss << "<tr><td colspan=\"2\">" <<
255 snippet.snippet << "</td></tr>" << endl;
256 continue;
257 }
258 list<string> lr;
259 if (!g_hiliter.plaintorich(snippet.snippet, lr, hdata)) {
260 LOGDEB1("No match for [" << snippet.snippet << "]\n");
261 continue;
262 }
263 nomatch = false;
264 oss << "<tr><td>";
265 if (snippet.page > 0) {
266 oss << "<a href=\"http://h/P" << snippet.page << "T" <<
267 snippet.term << "\">"
268 << "P. " << snippet.page << "</a>";
269 }
270 oss << "</td><td>" << lr.front().c_str() << "</td></tr>" << endl;
271 }
272 oss << "</table>" << endl;
273 if (nomatch) {
274 oss.str("<html><head></head><body>\n");
275 oss << qs2utf8s(tr("<p>Sorry, no exact match was found within limits. "
276 "Probably the document is very big and the snippets "
277 "generator got lost in a maze...</p>"));
278 }
279 oss << "\n</body></html>";
280 #if defined(USING_WEBKIT) || defined(USING_WEBENGINE)
281 browser->setHtml(QString::fromUtf8(oss.str().c_str()));
282 #else
283 browser->clear();
284 browser->append(".");
285 browser->clear();
286 browser->insertHtml(QString::fromUtf8(oss.str().c_str()));
287 browser->moveCursor (QTextCursor::Start);
288 browser->ensureCursorVisible();
289 #endif
290 raise();
291 }
292
slotEditFind()293 void SnippetsW::slotEditFind()
294 {
295 searchFM->show();
296 searchLE->selectAll();
297 searchLE->setFocus();
298 }
299
slotEditFindNext()300 void SnippetsW::slotEditFindNext()
301 {
302 if (!searchFM->isVisible())
303 slotEditFind();
304
305 #if defined(USING_WEBKIT) || defined(USING_WEBENGINE)
306 browser->findText(searchLE->text());
307 #else
308 browser->find(searchLE->text());
309 #endif
310
311 }
312
slotEditFindPrevious()313 void SnippetsW::slotEditFindPrevious()
314 {
315 if (!searchFM->isVisible())
316 slotEditFind();
317
318 #if defined(USING_WEBKIT) || defined(USING_WEBENGINE)
319 browser->findText(searchLE->text(), QWEBPAGE::FindBackward);
320 #else
321 browser->find(searchLE->text(), QTextDocument::FindBackward);
322 #endif
323 }
324
slotSearchTextChanged(const QString & txt)325 void SnippetsW::slotSearchTextChanged(const QString& txt)
326 {
327 #if defined(USING_WEBKIT) || defined(USING_WEBENGINE)
328 browser->findText(txt);
329 #else
330 // Cursor thing is so that we don't go to the next occurrence with
331 // each character, but rather try to extend the current match
332 QTextCursor cursor = browser->textCursor();
333 cursor.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
334 browser->setTextCursor(cursor);
335 browser->find(txt, 0);
336 #endif
337 }
338
onLinkClicked(const QUrl & url)339 void SnippetsW::onLinkClicked(const QUrl &url)
340 {
341 string ascurl = qs2u8s(url.toString()).substr(9);
342 LOGDEB("Snippets::onLinkClicked: [" << ascurl << "]\n");
343
344 if (ascurl.size() > 3) {
345 int what = ascurl[0];
346 switch (what) {
347 case 'P':
348 {
349 string::size_type numpos = ascurl.find_first_of("0123456789");
350 if (numpos == string::npos)
351 return;
352 int page = atoi(ascurl.c_str() + numpos);
353 string::size_type termpos = ascurl.find_first_of("T");
354 string term;
355 if (termpos != string::npos)
356 term = ascurl.substr(termpos+1);
357 emit startNativeViewer(m_doc, page,
358 QString::fromUtf8(term.c_str()));
359 return;
360 }
361 }
362 }
363 LOGERR("Snippets::onLinkClicked: bad link [" << ascurl << "]\n");
364 }
365