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