1 #include "selection.h"
2 
3 using namespace std;
4 
5 
SelectionPart(Poppler::TextBox * box)6 SelectionPart::SelectionPart(Poppler::TextBox *box) :
7 		text_box(box) {
8 	bbox = text_box->boundingBox();
9 }
10 
~SelectionPart()11 SelectionPart::~SelectionPart() {
12 	Poppler::TextBox *next;
13 	do {
14 		next = text_box->nextWord();
15 		delete text_box;
16 		text_box = next;
17 	} while (next != NULL);
18 }
19 
add_word(Poppler::TextBox * box)20 void SelectionPart::add_word(Poppler::TextBox *box) {
21 	bbox = bbox.united(box->boundingBox());
22 }
23 
get_text() const24 Poppler::TextBox *SelectionPart::get_text() const {
25 	return text_box;
26 }
27 
get_bbox() const28 QRectF SelectionPart::get_bbox() const {
29 	return bbox;
30 }
31 
selection_less_y(const SelectionPart * a,const SelectionPart * b)32 bool selection_less_y(const SelectionPart *a, const SelectionPart *b) {
33 	return a->get_bbox().center().y() < b->get_bbox().center().y();
34 }
35 
36 
SelectionLine(SelectionPart * part)37 SelectionLine::SelectionLine(SelectionPart *part) {
38 	parts.push_back(part);
39 	bbox = part->get_bbox();
40 }
41 
~SelectionLine()42 SelectionLine::~SelectionLine() {
43 	Q_FOREACH(SelectionPart *p, parts) {
44 		delete p;
45 	}
46 }
47 
add_part(SelectionPart * part)48 void SelectionLine::add_part(SelectionPart *part) {
49 	parts.push_back(part);
50 	bbox = bbox.united(part->get_bbox());
51 }
52 
get_parts() const53 const QList<SelectionPart *> &SelectionLine::get_parts() const {
54 	return parts;
55 }
56 
get_bbox() const57 QRectF SelectionLine::get_bbox() const {
58 	return bbox;
59 }
60 
sort()61 void SelectionLine::sort() {
62 	stable_sort(parts.begin(), parts.end(), selection_less_x);
63 }
64 
selection_less_x(const SelectionPart * a,const SelectionPart * b)65 bool selection_less_x(const SelectionPart *a, const SelectionPart *b) {
66 	return a->get_bbox().center().x() < b->get_bbox().center().x();
67 }
68 
69 
find_part(bool from,enum Selection::Mode mode)70 void Cursor::find_part(bool from, enum Selection::Mode mode) {
71 	const QList<SelectionPart *> parts = selectionline->get_parts();
72 	// select beginning/end of line when gap between lines is big enough
73 	if (selectionline->get_bbox().top() - click.y() > selectionline->get_bbox().height()) {
74 		set_beginning_of_line(selectionline, from);
75 		return;
76 	}
77 	if (click.y() - selectionline->get_bbox().bottom() > selectionline->get_bbox().height()) {
78 		set_end_of_line(selectionline, from);
79 		return;
80 	}
81 
82 	if (mode == Selection::StartLine) {
83 		if (from) {
84 			set_beginning_of_line(selectionline, from);
85 		} else {
86 			set_end_of_line(selectionline, from);
87 		}
88 		return;
89 	}
90 
91 	if (from) { // selection grows to the left
92 		for (part = 0; part < parts.size(); part++) {
93 			if (click.x() <= parts.at(part)->get_bbox().right()) {
94 				break;
95 			}
96 		}
97 		if (part >= parts.size()) {
98 			part = parts.size() - 1;
99 		}
100 	} else { // selection grows to the right
101 		for (part = parts.size() - 1; part >= 0; part--) {
102 			if (click.x() >= parts.at(part)->get_bbox().left()) {
103 				break;
104 			}
105 		}
106 		if (part < 0) {
107 			part = 0;
108 		}
109 	}
110 	find_word(parts.at(part)->get_text(), from, mode);
111 }
112 
find_word(const Poppler::TextBox * text_box,bool from,enum Selection::Mode mode)113 void Cursor::find_word(const Poppler::TextBox *text_box, bool from, enum Selection::Mode mode) {
114 	word = 0;
115 	const Poppler::TextBox *next = text_box;
116 	if (from) {
117 		while (true) {
118 			if (click.x() <= next->boundingBox().right()) {
119 				break;
120 			}
121 
122 			if (next->nextWord() == NULL) {
123 				break;
124 			}
125 			next = next->nextWord();
126 			word++;
127 		}
128 	} else {
129 		const Poppler::TextBox *prev = next;
130 		while (next != NULL) {
131 			if (click.x() < next->boundingBox().left()) {
132 				if (word > 0) {
133 					word--;
134 					next = prev;
135 				}
136 				break;
137 			}
138 
139 			if (next->nextWord() == NULL) {
140 				break;
141 			}
142 			prev = next;
143 			next = next->nextWord();
144 			word++;
145 		}
146 	}
147 	if (mode == Selection::Start) {
148 		find_character(next, from);
149 	} else if (mode == Selection::StartWord) {
150 		if (from) {
151 			character = 0;
152 			inclusive = true;
153 			x = next->charBoundingBox(0).left();
154 		} else {
155 			character = next->text().size() - 1;
156 			inclusive = true;
157 			x = next->charBoundingBox(character).right();
158 		}
159 	}
160 }
161 
find_character(const Poppler::TextBox * text,bool from)162 void Cursor::find_character(const Poppler::TextBox *text, bool from) {
163 	if (from) { // selection grows to the left
164 		for (character = 0; character < text->text().size(); character++) {
165 			if (click.x() <= text->charBoundingBox(character).right()) {
166 				x = text->charBoundingBox(character).left();
167 				inclusive = true;
168 				break;
169 			}
170 		}
171 		if (character >= text->text().size()) {
172 			character = text->text().size() - 1;
173 			x = text->charBoundingBox(character).right();
174 			inclusive = false;
175 		}
176 	} else { // selection grows to the right
177 		for (character = text->text().size() - 1; character >= 0; character--) {
178 			if (click.x() >= text->charBoundingBox(character).left()) {
179 				x = text->charBoundingBox(character).right();
180 				inclusive = true;
181 				break;
182 			}
183 		}
184 		if (character < 0) {
185 			character = 0;
186 			x = text->charBoundingBox(character).left();
187 			inclusive = false;
188 		}
189 	}
190 }
191 
set_beginning_of_line(const SelectionLine * line,bool from)192 void Cursor::set_beginning_of_line(const SelectionLine *line, bool from) {
193 	part = 0;
194 	word = 0;
195 	character = 0;
196 	inclusive = from;
197 	x = line->get_parts().at(0)->get_text()->charBoundingBox(0).left();
198 }
199 
set_end_of_line(const SelectionLine * line,bool from)200 void Cursor::set_end_of_line(const SelectionLine *line, bool from) {
201 	part = line->get_parts().size() - 1;
202 	Poppler::TextBox *text_box = line->get_parts().at(part)->get_text();
203 	word = 0;
204 	while (text_box->nextWord() != NULL) {
205 		text_box = text_box->nextWord();
206 		word++;
207 	}
208 	character = text_box->text().size() - 1;
209 	inclusive = !from;
210 	x = text_box->charBoundingBox(character).right();
211 }
212 
increment()213 void Cursor::increment() {
214 	Poppler::TextBox *text_box = selectionline->get_parts().at(part)->get_text();
215 	for (int i = 0; i < word; i++) {
216 		text_box = text_box->nextWord();
217 	}
218 
219 	if (!inclusive) {
220 		inclusive = true;
221 		x = text_box->charBoundingBox(character).left();
222 		return;
223 	}
224 
225 	character++;
226 	if (character >= text_box->text().size()) {
227 		if (text_box->nextWord() == NULL) {
228 			part++;
229 			if (part >= selectionline->get_parts().size()) {
230 				part--;
231 				character--;
232 				inclusive = false;
233 				x = text_box->charBoundingBox(character).right();
234 				return;
235 			}
236 			word = 0;
237 			character = 0;
238 			inclusive = true;
239 			x = selectionline->get_parts().at(part)->get_text()->charBoundingBox(0).left();
240 			return;
241 		}
242 		text_box = text_box->nextWord();
243 		word++;
244 		character = 0;
245 		inclusive = true;
246 		x = text_box->charBoundingBox(0).left();
247 		return;
248 	}
249 	x = text_box->charBoundingBox(character).left();
250 }
251 
decrement()252 void Cursor::decrement() {
253 	Poppler::TextBox *text_box = selectionline->get_parts().at(part)->get_text();
254 	Poppler::TextBox *prev = text_box;
255 	for (int i = 0; i < word; i++) {
256 		prev = text_box;
257 		text_box = text_box->nextWord();
258 	}
259 
260 	if (!inclusive) {
261 		inclusive = true;
262 		x = text_box->charBoundingBox(character).right();
263 		return;
264 	}
265 
266 	character--;
267 	if (character < 0) {
268 		if (word == 0) {
269 			if (part == 0) {
270 				character = 0;
271 				inclusive = false;
272 				x = text_box->charBoundingBox(0).left();
273 				return;
274 			}
275 			part--;
276 			text_box = selectionline->get_parts().at(part)->get_text();
277 			word = 0;
278 			while (text_box->nextWord() != NULL) {
279 				text_box = text_box->nextWord();
280 				word++;
281 			}
282 			character = text_box->text().size() - 1;
283 			inclusive = true;
284 			x = text_box->charBoundingBox(character).right();
285 			return;
286 		}
287 		word--;
288 		character = prev->text().size() - 1;
289 		inclusive = true;
290 		x = prev->charBoundingBox(character).right();
291 		return;
292 	}
293 	x = text_box->charBoundingBox(character).right();
294 }
295 
296 
MouseSelection()297 MouseSelection::MouseSelection() :
298 		active(false) {
299 }
300 
set_cursor(const QList<SelectionLine * > * lines,pair<int,QPointF> pos,enum Selection::Mode _mode)301 void MouseSelection::set_cursor(const QList<SelectionLine *> *lines,
302 		pair<int, QPointF> pos, enum Selection::Mode _mode) {
303 	// first = true: first cursor that was created (beginning of selection)
304 	// from = true: cursor that comes first (from the top left)
305 	bool first = _mode != Selection::End;
306 	if (first) {
307 		mode = _mode;
308 	}
309 
310 	if (first) {
311 		active = false;
312 		reversed = false;
313 	} else {
314 		active = true;
315 	}
316 
317 	Cursor &c = cursor[first ? 0 : 1];
318 	c.page = pos.first;
319 	c.click = pos.second;
320 
321 	if (lines == NULL || lines->empty()) {
322 		return;
323 	}
324 
325 	c.line = bsearch(lines, c.click.y());
326 	if (c.line < lines->size() - 1) {
327 		// the click lies between line and line + 1
328 		float prev = lines->at(c.line)->get_bbox().center().y();
329 		float next = lines->at(c.line + 1)->get_bbox().center().y();
330 
331 		if (first) {
332 			// beginning of selection -> set to closest line
333 			if (c.click.y() - prev > next - c.click.y()) {
334 				c.line++;
335 			}
336 		} else {
337 			// continued selection
338 			// only set to closest line if the beginning is on that line
339 			// or if the current and next box overlap
340 			// else the selection direction has to be considered
341 			bool line_added = false;
342 			if (c.click.y() - prev > next - c.click.y()) {
343 				if (c.line + 1 == cursor[0].line ||
344 						lines->at(c.line)->get_bbox().bottom() > lines->at(c.line + 1)->get_bbox().top()) {
345 					c.line++;
346 					line_added = true;
347 				}
348 			}
349 			update_reversed(c, lines->at(c.line));
350 
351 			// inside actual box?
352 			if (!line_added) {
353 				if (lines->at(c.line)->get_bbox().bottom() <= lines->at(c.line + 1)->get_bbox().top()) {
354 					if (reversed && c.line < cursor[0].line) {
355 						if (c.click.y() > lines->at(c.line)->get_bbox().bottom()) {
356 							c.line++;
357 						}
358 					} else {
359 						if (c.click.y() >= lines->at(c.line + 1)->get_bbox().top()) {
360 							c.line++;
361 						}
362 					}
363 				}
364 			}
365 
366 			update_reversed(c, lines->at(c.line));
367 		}
368 	} else {
369 		// last line
370 		if (!first) {
371 			update_reversed(c, lines->at(c.line));
372 		}
373 	}
374 
375 	// find closest part/word/character
376 	c.selectionline = lines->at(c.line);
377 	c.find_part(first ^ reversed, mode);
378 
379 	// word or line selection -> set second cursor right away
380 	if (first && (mode == Selection::StartWord || mode == Selection::StartLine)) {
381 		cursor[1] = c;
382 		cursor[1].find_part(!first ^ reversed, mode);
383 		active = true;
384 	}
385 }
386 
get_cursor(bool from) const387 Cursor MouseSelection::get_cursor(bool from) const {
388 	if (from ^ reversed) {
389 		return cursor[0];
390 	} else {
391 		return cursor[1];
392 	}
393 }
394 
get_selection_text(int page,const QList<SelectionLine * > * lines) const395 QString MouseSelection::get_selection_text(int page, const QList<SelectionLine *> *lines) const {
396 	QString text;
397 	if (lines != NULL && lines->size() != 0 && is_active()) {
398 		Cursor from = get_cursor(true);
399 		Cursor to = get_cursor(false);
400 		if (from.page <= page && to.page >= page) {
401 			if (from.page < page) {
402 				from.line = 0;
403 				from.set_beginning_of_line(lines->at(from.line), true);
404 			}
405 			if (to.page > page) {
406 				to.line = lines->size() - 1;
407 				to.set_end_of_line(lines->at(to.line), false);
408 			}
409 
410 			bool add_space = false;
411 
412 			for (int line = from.line; line <= to.line; line++) {
413 				float last_x = 0;
414 				int last_x_index = -2;
415 				const QList<SelectionPart *> p = lines->at(line)->get_parts();
416 
417 				for (int part = from.part; part < p.size(); part++) {
418 					if (to.line == line && to.part < part) {
419 						break;
420 					}
421 					int word = 0;
422 					Poppler::TextBox *box = p.at(part)->get_text();
423 					while (box != NULL) {
424 						if (to.line == line && to.part == part && to.word < word) {
425 							break;
426 						}
427 
428 						if ((from.part == part && word >= from.word) || part > from.part) {
429 							QString tmp = box->text();
430 							if (to.line == line && to.part == part && to.word == word) {
431 								tmp.truncate(to.character + 1);
432 								if (!to.inclusive) {
433 									tmp.chop(1);
434 								}
435 							}
436 							if (from.line == line && from.part == part && from.word == word) {
437 								tmp.remove(0, from.character);
438 								if (!from.inclusive) {
439 									tmp.remove(0, 1);
440 								}
441 							}
442 
443 							if (add_space) {
444 								text += QChar::fromLatin1(' ');
445 								add_space = false;
446 							}
447 							// big gap in front of current box, add <tab>
448 							if (word == 0 && part == last_x_index + 1) {
449 								if (box->boundingBox().left() - last_x > box->boundingBox().height()) {
450 									text += QChar::fromLatin1('\t');
451 								}
452 							}
453 
454 							text += tmp;
455 							if (box->hasSpaceAfter()) {
456 								add_space = true;
457 							}
458 						}
459 
460 						last_x = box->boundingBox().right();
461 						last_x_index = part;
462 						box = box->nextWord();
463 						word++;
464 					}
465 				}
466 
467 				if (line + 1 < lines->size()) {
468 					from.set_beginning_of_line(lines->at(line + 1), true);
469 					if (line < to.line) {
470 						text += QChar::fromLatin1('\n');
471 					}
472 				}
473 			}
474 		}
475 	}
476 	return text;
477 }
478 
deactivate()479 void MouseSelection::deactivate() {
480 	active = false;
481 }
482 
is_active() const483 bool MouseSelection::is_active() const {
484 	return active;
485 }
486 
bsearch(const QList<SelectionLine * > * lines,float value)487 int MouseSelection::bsearch(const QList<SelectionLine *> *lines, float value) {
488 	int from = 0, to = lines->size();
489 	int mid;
490 
491 	if (to == 0) {
492 		return 0;
493 	}
494 
495 	if (value <= lines->at(0)->get_bbox().center().y()) {
496 		return 0;
497 	} else if (value > lines->at(to - 1)->get_bbox().center().y()) {
498 		return to - 1;
499 	}
500 
501 	while (to - from > 1) {
502 		mid = (from + to) / 2;
503 		float cur = lines->at(mid)->get_bbox().center().y();
504 		if (cur == value) {
505 			return mid;
506 		} else if (cur < value) {
507 			from = mid;
508 		} else {
509 			to = mid;
510 		}
511 	}
512 
513 	return from;
514 }
515 
update_reversed(Cursor & c,const SelectionLine * line)516 void MouseSelection::update_reversed(Cursor &c, const SelectionLine *line) {
517 	if (reversed != calculate_reversed(c, line)) {
518 		// flip reversed flag, adjust first cursor's character
519 		if (reversed) {
520 			cursor[0].increment();
521 		} else {
522 			cursor[0].decrement();
523 		}
524 		reversed = !reversed;
525 	}
526 }
527 
calculate_reversed(Cursor & c,const SelectionLine * line) const528 bool MouseSelection::calculate_reversed(Cursor &c, const SelectionLine *line) const {
529 	if (c.page < cursor[0].page) {
530 		return true;
531 	} else if (c.page > cursor[0].page) {
532 		return false;
533 	}
534 	// same page
535 	if (c.line < cursor[0].line) {
536 		return true;
537 	} else if (c.line > cursor[0].line) {
538 		return false;
539 	}
540 	// same line
541 	if (line->get_bbox().top() - c.click.y() > line->get_bbox().height()) {
542 		return true;
543 	}
544 	if (c.click.y() - line->get_bbox().bottom() > line->get_bbox().height()) {
545 		return false;
546 	}
547 	if (c.click.x() < cursor[0].x) {
548 		return true;
549 	} else {
550 		return false;
551 	}
552 }
553 
554