1 #include <iostream>
2 #include "search.h"
3 #include "canvas.h"
4 #include "viewer.h"
5 #include "config.h"
6 #include "util.h"
7 #include "resourcemanager.h"
8 #include "layout/layout.h"
9
10 using namespace std;
11
12
13 //==[ SearchWorker ]===========================================================
SearchWorker(SearchBar * _bar)14 SearchWorker::SearchWorker(SearchBar *_bar) :
15 stop(false),
16 die(false),
17 bar(_bar),
18 forward(true) {
19 }
20
run()21 void SearchWorker::run() {
22 while (1) {
23 bar->search_mutex.lock();
24 stop = false;
25 if (die) {
26 bar->search_mutex.unlock();
27 break;
28 }
29 // always clear results -> empty search == stop search
30 emit clear_hits();
31
32 // get search string
33 bar->term_mutex.lock();
34 if (bar->term.isEmpty()) {
35 bar->term_mutex.unlock();
36 emit update_label_text(QString::fromUtf8("done."));
37 continue;
38 }
39 int start = bar->start_page;
40 QString search_term = bar->term;
41 forward = bar->forward;
42 bar->term_mutex.unlock();
43
44 // check if term contains upper case letters; if so, do case sensitive search (smartcase)
45 bool has_upper_case = false;
46 for (QString::const_iterator it = search_term.begin(); it != search_term.end(); ++it) {
47 if (it->isUpper()) {
48 has_upper_case = true;
49 break;
50 }
51 }
52
53 #ifdef DEBUG
54 cerr << "'" << search_term.toUtf8().constData() << "'" << endl;
55 #endif
56 emit update_label_text(QString::fromUtf8("[%1] 0\% searched, 0 hits")
57 .arg(has_upper_case ? QString::fromUtf8("Case") : QString::fromUtf8("no case")));
58
59 // search all pages
60 int hit_count = 0;
61 int page = start;
62 do {
63 Poppler::Page *p = bar->doc->page(page);
64 if (p == NULL) {
65 cerr << "failed to load page " << page << endl;
66 continue;
67 }
68
69 // collect all occurrences
70 QList<QRectF> *hits = new QList<QRectF>;
71 // even newer interface
72 QList<QRectF> tmp = p->search(search_term,
73 has_upper_case ? (Poppler::Page::SearchFlags) 0 : Poppler::Page::IgnoreCase);
74 // TODO support Poppler::Page::WholeWords
75 hits->swap(tmp);
76 #ifdef DEBUG
77 if (hits->size() > 0) {
78 cerr << hits->size() << " hits on page " << page << endl;
79 }
80 #endif
81 delete p;
82
83 // clean up when interrupted
84 if (stop || die) {
85 delete hits;
86 break;
87 }
88
89 if (hits->size() > 0) {
90 hit_count += hits->size();
91 emit search_done(page, hits);
92 } else {
93 delete hits;
94 }
95
96 // update progress label next to the search bar
97 int percent;
98 if (forward) {
99 percent = page + bar->doc->numPages() - start;
100 } else {
101 percent = start + bar->doc->numPages() - page;
102 }
103 percent = (percent % bar->doc->numPages()) * 100 / bar->doc->numPages();
104 QString progress = QString::fromUtf8("[%1] %2\% searched, %3 hits")
105 .arg(has_upper_case ? QString::fromUtf8("Case") : QString::fromUtf8("no case"))
106 .arg(percent)
107 .arg(hit_count);
108 emit update_label_text(progress);
109
110 if (forward) {
111 if (++page == bar->doc->numPages()) {
112 page = 0;
113 }
114 } else {
115 if (--page == -1) {
116 page = bar->doc->numPages() - 1;
117 }
118 }
119 } while (page != start);
120 #ifdef DEBUG
121 cerr << "done!" << endl;
122 #endif
123 emit update_label_text(QString::fromUtf8("[%1] done, %2 hits")
124 .arg(has_upper_case ? QString::fromUtf8("Case") : QString::fromUtf8("no case"))
125 .arg(hit_count));
126 }
127 }
128
129
130 //==[ SearchBar ]==============================================================
SearchBar(const QString & file,Viewer * v,QWidget * parent)131 SearchBar::SearchBar(const QString &file, Viewer *v, QWidget *parent) :
132 QWidget(parent),
133 viewer(v) {
134 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
135 line = new QLineEdit(parent);
136
137 progress = new QLabel(QString::fromUtf8("done."));
138 progress->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
139
140 layout = new QHBoxLayout();
141 layout->setContentsMargins(0, 0, 0, 0);
142 layout->setSpacing(0);
143 layout->addWidget(line);
144 layout->addWidget(progress);
145 setLayout(layout);
146
147 initialize(file, QByteArray());
148 }
149
initialize(const QString & file,const QByteArray & password)150 void SearchBar::initialize(const QString &file, const QByteArray &password) {
151 worker = NULL;
152
153 doc = NULL;
154 // if (!file.isNull()) { // don't print the poppler error message for the second time
155 if (!file.isEmpty()) {
156 doc = Poppler::Document::load(file, QByteArray(), password);
157 }
158
159 if (doc == NULL) {
160 // poppler already prints a debug message
161 return;
162 }
163 if (doc->isLocked()) {
164 // poppler already prints a debug message
165 // cerr << "missing password" << endl;
166 delete doc;
167 doc = NULL;
168 return;
169 }
170 worker = new SearchWorker(this);
171 worker->start();
172
173 connect(line, SIGNAL(returnPressed()), this, SLOT(set_text()),
174 Qt::UniqueConnection);
175 connect(worker, SIGNAL(update_label_text(const QString &)),
176 progress, SLOT(setText(const QString &)), Qt::UniqueConnection);
177 connect(worker, SIGNAL(search_done(int, QList<QRectF> *)),
178 this, SLOT(insert_hits(int, QList<QRectF> *)), Qt::UniqueConnection);
179 connect(worker, SIGNAL(clear_hits()),
180 this, SLOT(clear_hits()), Qt::UniqueConnection);
181 }
182
~SearchBar()183 SearchBar::~SearchBar() {
184 shutdown();
185 delete layout;
186 delete progress;
187 delete line;
188 }
189
shutdown()190 void SearchBar::shutdown() {
191 if (worker != NULL) {
192 join_threads();
193 }
194 if (doc == NULL) {
195 return;
196 }
197 delete doc;
198 delete worker;
199 }
200
load(const QString & file,const QByteArray & password)201 void SearchBar::load(const QString &file, const QByteArray &password) {
202 shutdown();
203 initialize(file, password);
204 }
205
is_valid() const206 bool SearchBar::is_valid() const {
207 return doc != NULL;
208 }
209
focus(bool forward)210 void SearchBar::focus(bool forward) {
211 forward_tmp = forward; // only apply when the user presses enter
212 line->activateWindow();
213 line->setText(term);
214 line->setFocus(Qt::OtherFocusReason);
215 line->selectAll();
216 show();
217 }
218
get_hits() const219 const std::map<int,QList<QRectF> *> *SearchBar::get_hits() const {
220 return &hits;
221 }
222
is_search_forward() const223 bool SearchBar::is_search_forward() const {
224 return forward;
225 }
226
event(QEvent * event)227 bool SearchBar::event(QEvent *event) {
228 if (event->type() == QEvent::Hide) {
229 viewer->get_canvas()->set_search_visible(false);
230 return true;
231 } else if (event->type() == QEvent::Show) {
232 viewer->get_canvas()->set_search_visible(true);
233 return true;
234 }
235 return QWidget::event(event);
236 }
237
reset_search()238 void SearchBar::reset_search() {
239 clear_hits();
240 term = QString();
241 progress->setText(QString::fromUtf8("done."));
242 viewer->get_canvas()->set_search_visible(false);
243 viewer->get_canvas()->setFocus(Qt::OtherFocusReason);
244 hide();
245 }
246
insert_hits(int page,QList<QRectF> * l)247 void SearchBar::insert_hits(int page, QList<QRectF> *l) {
248 bool empty = hits.empty();
249
250 map<int,QList<QRectF> *>::iterator it = hits.find(page);
251 if (it != hits.end()) {
252 delete it->second;
253 }
254 hits[page] = l;
255
256 if (viewer->get_canvas()->get_layout()->page_visible(page)) {
257 viewer->get_canvas()->update();
258 }
259
260 // only update the layout if the hits should be viewed
261 if (empty) {
262 viewer->get_canvas()->get_layout()->update_search();
263 }
264 }
265
clear_hits()266 void SearchBar::clear_hits() {
267 for (map<int,QList<QRectF> *>::iterator it = hits.begin(); it != hits.end(); ++it) {
268 delete it->second;
269 }
270 hits.clear();
271 viewer->get_canvas()->update();
272 }
273
set_text()274 void SearchBar::set_text() {
275 // prevent searching a non-existing document
276 if (!is_valid()) {
277 return;
278 }
279
280 forward = forward_tmp;
281 Canvas *c = viewer->get_canvas();
282 // do not start the same search again but signal slots
283 if (term == line->text()) {
284 c->setFocus(Qt::OtherFocusReason);
285 c->get_layout()->update_search();
286 return;
287 }
288
289 term_mutex.lock();
290 start_page = c->get_layout()->get_page();
291 term = line->text();
292 term_mutex.unlock();
293
294 worker->stop = true;
295 search_mutex.unlock();
296 c->setFocus(Qt::OtherFocusReason);
297 }
298
join_threads()299 void SearchBar::join_threads() {
300 worker->die = true;
301 search_mutex.unlock();
302 worker->wait();
303 }
304
305