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