1 /**
2 * Copyright (C) 2012 Stefan Löffler
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2, or (at your option) any later
7 * version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 */
14 #include <PDFDocumentTools.h>
15 #include <PDFDocumentView.h>
16
17 namespace QtPDF {
18 namespace DocumentTool {
19
20 // AbstractTool
21 // ========================
22 //
arm()23 void AbstractTool::arm() {
24 Q_ASSERT(_parent != NULL);
25 if (_parent->viewport())
26 _parent->viewport()->setCursor(_cursor);
27 }
disarm()28 void AbstractTool::disarm() {
29 Q_ASSERT(_parent != NULL);
30 if (_parent->viewport())
31 _parent->viewport()->unsetCursor();
32 }
33
keyPressEvent(QKeyEvent * event)34 void AbstractTool::keyPressEvent(QKeyEvent *event)
35 {
36 if (_parent)
37 _parent->maybeArmTool(Qt::LeftButton + event->modifiers());
38 }
39
keyReleaseEvent(QKeyEvent * event)40 void AbstractTool::keyReleaseEvent(QKeyEvent *event)
41 {
42 if (_parent)
43 _parent->maybeArmTool(Qt::LeftButton + event->modifiers());
44 }
45
mousePressEvent(QMouseEvent * event)46 void AbstractTool::mousePressEvent(QMouseEvent * event)
47 {
48 if (_parent)
49 _parent->maybeArmTool(event->buttons() | event->modifiers());
50 }
51
mouseReleaseEvent(QMouseEvent * event)52 void AbstractTool::mouseReleaseEvent(QMouseEvent * event)
53 {
54 // If the last mouse button was released, we arm the tool corresponding to the
55 // left mouse button by default
56 Qt::MouseButtons buttons = event->buttons();
57 if (buttons == Qt::NoButton)
58 buttons |= Qt::LeftButton;
59
60 if (_parent)
61 _parent->maybeArmTool(buttons | event->modifiers());
62 }
63
64
65 // ZoomIn
66 // ========================
67 //
ZoomIn(PDFDocumentView * parent)68 ZoomIn::ZoomIn(PDFDocumentView * parent)
69 : AbstractTool(parent),
70 _started(false)
71 {
72 _cursor = QCursor(QPixmap(QString::fromUtf8(":/QtPDF/icons/zoomincursor.png")));
73 }
74
mousePressEvent(QMouseEvent * event)75 void ZoomIn::mousePressEvent(QMouseEvent * event)
76 {
77 Q_ASSERT(_parent != NULL);
78
79 if (!event)
80 return;
81 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
82 if (_started)
83 _startPos = event->pos();
84 }
85
mouseReleaseEvent(QMouseEvent * event)86 void ZoomIn::mouseReleaseEvent(QMouseEvent * event)
87 {
88 Q_ASSERT(_parent != NULL);
89
90 if (!event || !_started)
91 return;
92 if (event->buttons() == Qt::NoButton && event->button() == Qt::LeftButton) {
93 QPoint offset = event->pos() - _startPos;
94 if (offset.manhattanLength() < QApplication::startDragDistance())
95 _parent->zoomIn(QGraphicsView::AnchorUnderMouse);
96 }
97 _started = false;
98 }
99
100 // ZoomOut
101 // ========================
102 //
ZoomOut(PDFDocumentView * parent)103 ZoomOut::ZoomOut(PDFDocumentView * parent)
104 : AbstractTool(parent),
105 _started(false)
106 {
107 _cursor = QCursor(QPixmap(QString::fromUtf8(":/QtPDF/icons/zoomoutcursor.png")));
108 }
109
mousePressEvent(QMouseEvent * event)110 void ZoomOut::mousePressEvent(QMouseEvent * event)
111 {
112 if (!event)
113 return;
114 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
115 if (_started)
116 _startPos = event->pos();
117 }
118
mouseReleaseEvent(QMouseEvent * event)119 void ZoomOut::mouseReleaseEvent(QMouseEvent * event)
120 {
121 Q_ASSERT(_parent != NULL);
122
123 if (!event || !_started)
124 return;
125 if (event->buttons() == Qt::NoButton && event->button() == Qt::LeftButton) {
126 QPoint offset = event->pos() - _startPos;
127 if (offset.manhattanLength() < QApplication::startDragDistance())
128 _parent->zoomOut(QGraphicsView::AnchorUnderMouse);
129 }
130 _started = false;
131 }
132
133
134 // MagnifyingGlass
135 // ==============================
136 //
MagnifyingGlass(PDFDocumentView * parent)137 MagnifyingGlass::MagnifyingGlass(PDFDocumentView * parent) :
138 AbstractTool(parent),
139 _started(false)
140 {
141 _magnifier = new PDFDocumentMagnifierView(parent);
142 _cursor = QCursor(QPixmap(QString::fromUtf8(":/QtPDF/icons/magnifiercursor.png")));
143 }
144
setMagnifierShape(const MagnifierShape shape)145 void MagnifyingGlass::setMagnifierShape(const MagnifierShape shape)
146 {
147 Q_ASSERT(_magnifier != NULL);
148 _magnifier->setShape(shape);
149 }
150
setMagnifierSize(const int size)151 void MagnifyingGlass::setMagnifierSize(const int size)
152 {
153 Q_ASSERT(_magnifier != NULL);
154 _magnifier->setSize(size);
155 }
156
mousePressEvent(QMouseEvent * event)157 void MagnifyingGlass::mousePressEvent(QMouseEvent * event)
158 {
159 Q_ASSERT(_magnifier != NULL);
160 if (!event)
161 return;
162 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
163
164 if (_started) {
165 _magnifier->prepareToShow();
166 _magnifier->setPosition(event->pos());
167 }
168 _magnifier->setVisible(_started);
169
170 // Ensure an update of the viewport so that the drop shadow is painted
171 // correctly
172 QRect r(QPoint(0, 0), _magnifier->dropShadow().size());
173 r.moveCenter(_magnifier->geometry().center());
174 _parent->viewport()->update(r);
175 }
176
mouseMoveEvent(QMouseEvent * event)177 void MagnifyingGlass::mouseMoveEvent(QMouseEvent * event)
178 {
179 Q_ASSERT(_magnifier != NULL);
180 Q_ASSERT(_parent != NULL);
181
182 if (!event || !_started)
183 return;
184
185 // Only update the portion of the viewport (possibly) obscured by the
186 // magnifying glass and its shadow.
187 QRect r(QPoint(0, 0), _magnifier->dropShadow().size());
188 r.moveCenter(_magnifier->geometry().center());
189 _parent->viewport()->update(r);
190
191 _magnifier->setPosition(event->pos());
192 }
193
mouseReleaseEvent(QMouseEvent * event)194 void MagnifyingGlass::mouseReleaseEvent(QMouseEvent * event)
195 {
196 Q_ASSERT(_magnifier != NULL);
197
198 if (!event || !_started)
199 return;
200 if (event->buttons() == Qt::NoButton && event->button() == Qt::LeftButton) {
201 _magnifier->hide();
202 _started = false;
203 // Force an update of the viewport so that the drop shadow is hidden
204 QRect r(QPoint(0, 0), _magnifier->dropShadow().size());
205 r.moveCenter(_magnifier->geometry().center());
206 _parent->viewport()->update(r);
207 }
208 }
209
paintEvent(QPaintEvent * event)210 void MagnifyingGlass::paintEvent(QPaintEvent * event)
211 {
212 Q_ASSERT(_magnifier != NULL);
213 Q_ASSERT(_parent != NULL);
214
215 if (!_started)
216 return;
217
218 // Draw a drop shadow
219 QPainter p(_parent->viewport());
220 QPixmap& dropShadow(_magnifier->dropShadow());
221 QRect r(QPoint(0, 0), dropShadow.size());
222 r.moveCenter(_magnifier->geometry().center());
223 p.drawPixmap(r.topLeft(), dropShadow);
224 }
225
226
227 // MarqueeZoom
228 // ==========================
229 //
MarqueeZoom(PDFDocumentView * parent)230 MarqueeZoom::MarqueeZoom(PDFDocumentView * parent) :
231 AbstractTool(parent),
232 _started(false)
233 {
234 Q_ASSERT(_parent);
235 _rubberBand = new QRubberBand(QRubberBand::Rectangle, _parent->viewport());
236 _cursor = QCursor(Qt::CrossCursor);
237 }
238
mousePressEvent(QMouseEvent * event)239 void MarqueeZoom::mousePressEvent(QMouseEvent * event)
240 {
241 Q_ASSERT(_parent != NULL);
242 Q_ASSERT(_rubberBand != NULL);
243
244 if (!event)
245 return;
246 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
247 if (_started) {
248 _startPos = event->pos();
249 _rubberBand->setGeometry(QRect());
250 _rubberBand->show();
251 }
252 }
253
mouseMoveEvent(QMouseEvent * event)254 void MarqueeZoom::mouseMoveEvent(QMouseEvent * event)
255 {
256 Q_ASSERT(_rubberBand != NULL);
257
258 QPoint o = _startPos, p = event->pos();
259
260 if (event->buttons() != Qt::LeftButton ) {
261 // The user somehow let go of the left button without us recieving an
262 // event. Abort the zoom operation.
263 _rubberBand->setGeometry(QRect());
264 _rubberBand->hide();
265 } else if ( (o - p).manhattanLength() > QApplication::startDragDistance() ) {
266 // Update rubber band Geometry.
267 _rubberBand->setGeometry(QRect(
268 QPoint(qMin(o.x(),p.x()), qMin(o.y(), p.y())),
269 QPoint(qMax(o.x(),p.x()), qMax(o.y(), p.y()))
270 ));
271 }
272 }
273
mouseReleaseEvent(QMouseEvent * event)274 void MarqueeZoom::mouseReleaseEvent(QMouseEvent * event)
275 {
276 Q_ASSERT(_parent != NULL);
277
278 if (!event || !_started)
279 return;
280 if (event->buttons() == Qt::NoButton && event->button() == Qt::LeftButton) {
281 QRectF zoomRect = _parent->mapToScene(_rubberBand->geometry()).boundingRect();
282 _rubberBand->hide();
283 _rubberBand->setGeometry(QRect());
284 _parent->zoomToRect(zoomRect);
285 }
286 _started = false;
287 }
288
289
290 // Move
291 // ===================
292 //
Move(PDFDocumentView * parent)293 Move::Move(PDFDocumentView * parent) :
294 AbstractTool(parent),
295 _started(false)
296 {
297 _cursor = QCursor(Qt::OpenHandCursor);
298 _closedHandCursor = QCursor(Qt::ClosedHandCursor);
299 }
300
mousePressEvent(QMouseEvent * event)301 void Move::mousePressEvent(QMouseEvent * event)
302 {
303 Q_ASSERT(_parent != NULL);
304
305 if (!event)
306 return;
307 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
308 if (_started) {
309 if (_parent->viewport())
310 _parent->viewport()->setCursor(_closedHandCursor);
311 _oldPos = event->pos();
312 }
313 }
314
mouseMoveEvent(QMouseEvent * event)315 void Move::mouseMoveEvent(QMouseEvent * event)
316 {
317 Q_ASSERT(_parent != NULL);
318
319 if (!_started || !event)
320 return;
321
322 // Adapted from <qt>/src/gui/graphicsview/qgraphicsview.cpp @ QGraphicsView::mouseMoveEvent
323 QScrollBar *hBar = _parent->horizontalScrollBar();
324 QScrollBar *vBar = _parent->verticalScrollBar();
325 QPoint delta = event->pos() - _oldPos;
326 hBar->setValue(hBar->value() - delta.x());
327 vBar->setValue(vBar->value() - delta.y());
328 _oldPos = event->pos();
329 }
330
mouseReleaseEvent(QMouseEvent * event)331 void Move::mouseReleaseEvent(QMouseEvent * event)
332 {
333 Q_ASSERT(_parent != NULL);
334
335 if (!event || !_started)
336 return;
337 if (event->buttons() == Qt::NoButton && event->button() == Qt::LeftButton)
338 if (_parent->viewport())
339 _parent->viewport()->setCursor(_cursor);
340 _started = false;
341 }
342
343
344 // ContextClick
345 // ===========================
346 //
mousePressEvent(QMouseEvent * event)347 void ContextClick::mousePressEvent(QMouseEvent * event)
348 {
349 Q_ASSERT(_parent != NULL);
350
351 if (!event)
352 return;
353 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
354 }
355
mouseReleaseEvent(QMouseEvent * event)356 void ContextClick::mouseReleaseEvent(QMouseEvent * event)
357 {
358 Q_ASSERT(_parent != NULL);
359
360 if (!event || !_started)
361 return;
362
363 _started = false;
364 if (event->buttons() == Qt::NoButton && event->button() == Qt::LeftButton) {
365 QPointF pos(_parent->mapToScene(event->pos()));
366
367 PDFPageGraphicsItem * pageItem = NULL;
368 foreach(QGraphicsItem * item, _parent->scene()->items(pos, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder)) {
369 if (item && item->type() == PDFPageGraphicsItem::Type) {
370 pageItem = static_cast<PDFPageGraphicsItem*>(item);
371 break;
372 }
373 }
374 if (!pageItem)
375 return;
376 _parent->triggerContextClick(pageItem->pageNum(), pageItem->mapToPage(pageItem->mapFromScene(pos)));
377 }
378 }
379
380 // MeasureLineGrip
381 // ===========================
382 //
MeasureLineGrip(MeasureLine * parent,const int pt)383 MeasureLineGrip::MeasureLineGrip(MeasureLine * parent, const int pt) :
384 QGraphicsRectItem(parent),
385 _pt(pt)
386 {
387 setFlag(QGraphicsItem::ItemIgnoresTransformations);
388 setAcceptHoverEvents(true);
389 setAcceptedMouseButtons(Qt::LeftButton);
390 setRect(-2, -2, 5, 5);
391 setBrush(QBrush(Qt::green));
392 setPen(QPen(Qt::black));
393 }
394
hoverEnterEvent(QGraphicsSceneHoverEvent * event)395 void MeasureLineGrip::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
396 {
397 setCursor(Qt::CrossCursor);
398 QGraphicsRectItem::hoverEnterEvent(event);
399 }
400
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)401 void MeasureLineGrip::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
402 {
403 unsetCursor();
404 QGraphicsRectItem::hoverLeaveEvent(event);
405 }
406
mousePressEvent(QGraphicsSceneMouseEvent * event)407 void MeasureLineGrip::mousePressEvent(QGraphicsSceneMouseEvent *event)
408 {
409 // mousePressEvent must be implemented to receive mouseMoveEvent messages
410 }
411
mouseMoveEvent(QGraphicsSceneMouseEvent * event)412 void MeasureLineGrip::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
413 {
414 Q_ASSERT(event != NULL);
415 mouseMove(event->scenePos(), event->modifiers());
416 }
417
mouseMove(const QPointF scenePos,const Qt::KeyboardModifiers modifiers)418 void MeasureLineGrip::mouseMove(const QPointF scenePos, const Qt::KeyboardModifiers modifiers)
419 {
420 MeasureLine * ml = static_cast<MeasureLine*>(parentItem());
421 Q_ASSERT(ml != NULL);
422
423 switch(_pt) {
424 case 1:
425 {
426 QLineF line(scenePos, ml->line().p2());
427 if (modifiers.testFlag(Qt::ControlModifier)) {
428 // locked mode; horizontal or vertical line only
429 if (line.angle() <= 45 || line.angle() >= 315 ||
430 (line.angle() >= 135 && line.angle() <= 225))
431 line.setP1(QPointF(line.p1().x(), line.p2().y()));
432 else
433 line.setP1(QPointF(line.p2().x(), line.p1().y()));
434 }
435 ml->setLine(line);
436 break;
437 }
438 default:
439 {
440 QLineF line(ml->line().p1(), scenePos);
441 if (modifiers.testFlag(Qt::ControlModifier)) {
442 // locked mode; horizontal or vertical line only
443 if (line.angle() <= 45 || line.angle() >= 315 ||
444 (line.angle() >= 135 && line.angle() <= 225))
445 line.setP2(QPointF(line.p2().x(), line.p1().y()));
446 else
447 line.setP2(QPointF(line.p1().x(), line.p2().y()));
448 }
449 ml->setLine(line);
450 break;
451 }
452 }
453 }
454
455 // MeasureLine
456 // ===========================
457 //
MeasureLine(QGraphicsView * primaryView,QGraphicsItem * parent)458 MeasureLine::MeasureLine(QGraphicsView * primaryView, QGraphicsItem * parent /* = NULL */) :
459 QGraphicsLineItem(parent),
460 _primaryView(primaryView)
461 {
462 _measureBox = new QComboBox();
463 _measureBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
464 _measureBoxProxy = new QGraphicsProxyWidget(this);
465 _measureBoxProxy->setWidget(_measureBox);
466 _measureBoxProxy->setFlag(QGraphicsItem::ItemIgnoresTransformations);
467 _measureBoxProxy->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
468
469 setAcceptHoverEvents(true);
470 setAcceptedMouseButtons(Qt::LeftButton);
471
472 _grip1 = new MeasureLineGrip(this, 1);
473 _grip2 = new MeasureLineGrip(this, 2);
474 }
475
setLine(QLineF line)476 void MeasureLine::setLine(QLineF line)
477 {
478 QGraphicsLineItem::setLine(line);
479
480 _grip1->setPos(line.p1());
481 _grip2->setPos(line.p2());
482 updateMeasurement();
483 }
484
updateMeasurement()485 void MeasureLine::updateMeasurement()
486 {
487 Q_ASSERT(_measureBox != NULL);
488
489 // Note: we use LaTeX units here, i.e., 1 pt = 1/72.27 in (as opposed to the
490 // pdf unit 1 pt = 1/72 in, which in this context is called 1 bp); see
491 // http://en.wikibooks.org/wiki/LaTeX/Useful_Measurement_Macros
492
493 // NOTE: The view internally uses coordinates scaled by DPI/72 (owing to the
494 // PDF convention of 1 in = 72 pt). We have to undo that scaling here to get
495 // physical units.
496 float dx = line().dx() / QApplication::desktop()->physicalDpiX();
497 float dy = line().dy() / QApplication::desktop()->physicalDpiY();
498 // length: Length of the measurement line in pt (i.e., 1/72.27 inch)
499 float length = 72.27 * qSqrt(dx * dx + dy * dy);
500
501 int idx = _measureBox->currentIndex();
502 _measureBox->clear();
503 _measureBox->addItem(QString::fromUtf8("%1 pt").arg(length), QString::fromUtf8("pt"));
504 _measureBox->addItem(QString::fromUtf8("%1 mm").arg(length / 2.84), QString::fromUtf8("mm"));
505 _measureBox->addItem(QString::fromUtf8("%1 cm").arg(length / 28.4), QString::fromUtf8("cm"));
506 _measureBox->addItem(QString::fromUtf8("%1 in").arg(length / 72.27), QString::fromUtf8("in"));
507 _measureBox->addItem(QString::fromUtf8("%1 bp").arg(length * 1.00375), QString::fromUtf8("bp"));
508 _measureBox->addItem(QString::fromUtf8("%1 pc").arg(length / 12), QString::fromUtf8("pc"));
509 _measureBox->addItem(QString::fromUtf8("%1 dd").arg(length / 1.07), QString::fromUtf8("dd"));
510 _measureBox->addItem(QString::fromUtf8("%1 cc").arg(length / 12.84), QString::fromUtf8("cc"));
511 _measureBox->addItem(QString::fromUtf8("%1 sp").arg(length * 65536), QString::fromUtf8("sp"));
512 if (idx < 0 || idx >= _measureBox->count())
513 idx = 0;
514 _measureBox->setCurrentIndex(idx);
515 _measureBox->updateGeometry();
516 // Since the box size may have changed, we need to reposition the box
517 updateMeasureBoxPos();
518 }
519
updateMeasureBoxPos()520 void MeasureLine::updateMeasureBoxPos()
521 {
522 const float ALMOST_ZERO = 1e-4;
523 Q_ASSERT(_primaryView != NULL);
524 Q_ASSERT(_measureBoxProxy != NULL);
525 Q_ASSERT(_measureBox != NULL);
526
527 QPointF center = line().pointAt(0.5);
528 // scaling of a unit square
529 float scaling = _primaryView->mapToScene(0, 0, 1, 1).boundingRect().width();
530 // spacing of 2 pixels (mapped to scene coordinates)
531 float spacing = 2 * scaling;
532 QPointF offset;
533
534 // Get the size of the measurement box in scene coordinates
535 QSizeF sceneSize = scaling * _measureBox->size();
536
537 // horizontal line
538 if ((line().angle() <= ALMOST_ZERO || line().angle() > 360 - ALMOST_ZERO) ||
539 (line().angle() >= 180 - ALMOST_ZERO && line().angle() < 180 + ALMOST_ZERO))
540 offset = QPointF(-sceneSize.width() / 2., spacing);
541 // vertical line
542 else if ((line().angle() >= 90 - ALMOST_ZERO && line().angle() < 90 + ALMOST_ZERO) ||
543 (line().angle() >= 270 - ALMOST_ZERO && line().angle() < 270 + ALMOST_ZERO))
544 offset = QPointF(spacing, -sceneSize.height() / 2);
545 // line pointing up
546 else if (line().angle() < 90 || (line().angle() > 180 && line().angle() < 270))
547 offset = QPointF(spacing / 1.41421356237, spacing / 1.41421356237);
548 // line pointing down
549 else
550 offset = QPointF(spacing / 1.41421356237, -sceneSize.height() - spacing / 1.41421356237);
551
552 _measureBoxProxy->setPos(center + offset);
553 }
554
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)555 void MeasureLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
556 {
557 QGraphicsLineItem::paint(painter, option, widget);
558 // TODO: Possibly change style of pen
559
560 // TODO: Only reposition measurement box if zoom level changed
561 updateMeasureBoxPos();
562 }
563
hoverEnterEvent(QGraphicsSceneHoverEvent * event)564 void MeasureLine::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
565 {
566 setCursor(Qt::SizeAllCursor);
567 }
568
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)569 void MeasureLine::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
570 {
571 unsetCursor();
572 }
573
mousePressEvent(QGraphicsSceneMouseEvent * event)574 void MeasureLine::mousePressEvent(QGraphicsSceneMouseEvent *event)
575 {
576 // Find the offset of the grab point from handle 1
577 _grabOffset = event->scenePos() - line().p1();
578 }
579
mouseMoveEvent(QGraphicsSceneMouseEvent * event)580 void MeasureLine::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
581 {
582 // Note: We only receive this event while dragging (i.e., after a
583 // mousePressEvent)
584 setLine(QLineF(event->scenePos() - _grabOffset, event->scenePos() - _grabOffset + line().p2() - line().p1()));
585 }
586
587
588 // Measure
589 // ===========================
590 //
Measure(PDFDocumentView * parent)591 Measure::Measure(PDFDocumentView * parent) :
592 AbstractTool(parent),
593 _measureLine(NULL),
594 _started(false)
595 {
596 }
597
mousePressEvent(QMouseEvent * event)598 void Measure::mousePressEvent(QMouseEvent * event)
599 {
600 Q_ASSERT(_parent != NULL);
601 if (event && _parent->scene()) {
602 _started = (event->buttons() == Qt::LeftButton && event->button() == Qt::LeftButton);
603 if (!_started)
604 return;
605 if (!_measureLine) {
606 _measureLine = new MeasureLine(_parent);
607 _parent->scene()->addItem(_measureLine);
608 }
609 _measureLine->setLine(_parent->mapToScene(event->pos()), _parent->mapToScene(event->pos()));
610 // Initialize with a hidden measuring line with length 0. This way, simple
611 // clicking can be used to remove (i.e., hide) a previously visible line
612 _measureLine->hide();
613 _started = true;
614 _startPos = event->pos();
615 }
616 }
617
mouseMoveEvent(QMouseEvent * event)618 void Measure::mouseMoveEvent(QMouseEvent *event)
619 {
620 if (_started) {
621 Q_ASSERT(_measureLine != NULL);
622 Q_ASSERT(_measureLine->_grip2 != NULL);
623 _measureLine->_grip2->mouseMove(_parent->mapToScene(event->pos()), event->modifiers());
624 if ((event->pos() - _startPos).manhattanLength() > QApplication::startDragDistance() && !_measureLine->isVisible())
625 _measureLine->show();
626 }
627 }
628
mouseReleaseEvent(QMouseEvent * event)629 void Measure::mouseReleaseEvent(QMouseEvent * event)
630 {
631 _started = false;
632 }
633
keyPressEvent(QKeyEvent * event)634 void Measure::keyPressEvent(QKeyEvent *event)
635 {
636 // We need to hijack the Ctrl key modifier here (if the tool is started;
637 // otherwise we pass it on (e.g., to a DocumentTool::ContextClick))
638 if (event->key() != Qt::Key_Control || !_started)
639 AbstractTool::keyPressEvent(event);
640 }
641
keyReleaseEvent(QKeyEvent * event)642 void Measure::keyReleaseEvent(QKeyEvent *event)
643 {
644 // We need to hijack the Ctrl key modifier here (if the tool is started;
645 // otherwise we pass it on (e.g., to a DocumentTool::ContextClick))
646 if (event->key() != Qt::Key_Control || !_started)
647 AbstractTool::keyReleaseEvent(event);
648 }
649
650 // Select
651 // ========================
652 //
653
654 // distanceFromRect() computes the Manhatten distance between a point and a
655 // rectangle. If the point is inside (or on the border of) the rectangle, the
656 // function returns 0. Otherwise, it returns the smallest Manhatten distance of
657 // pt to any point on the border of the rectangle.
distanceFromRect(const QPointF & pt,const QRectF & rect)658 inline double distanceFromRect(const QPointF & pt, const QRectF & rect) {
659 double dx, dy;
660 if (pt.x() < rect.left())
661 dx = rect.left() - pt.x();
662 else if (pt.x() > rect.right())
663 dx = pt.x() - rect.right();
664 else
665 dx = 0;
666 if (pt.y() < rect.top())
667 dy = rect.top() - pt.y();
668 else if (pt.y() > rect.bottom())
669 dy = pt.y() - rect.bottom();
670 else
671 dy = 0;
672 return dx + dy;
673 }
674
Select(PDFDocumentView * parent)675 Select::Select(PDFDocumentView * parent) :
676 AbstractTool(parent),
677 _cursorOverBox(false),
678 _highlightPath(NULL),
679 _mouseMode(MouseMode_None),
680 _rubberBand(NULL),
681 _pageNum(-1),
682 _startBox(0),
683 _startSubbox(0)
684 {
685 // We default to the cross cursor. Only when over a text box we change to the
686 // IBeam cursor
687 _cursor = QCursor(Qt::CrossCursor);
688 // Default to the system highlighting color, with alpha set to 50% (since we
689 // simply superimpose the selection on the page images)
690 _highlightColor = QApplication::palette().color(QPalette::Highlight);
691 _highlightColor.setAlpha(128);
692 }
693
~Select()694 Select::~Select()
695 {
696 if (_highlightPath)
697 delete _highlightPath;
698 if (_rubberBand)
699 delete _rubberBand;
700 }
701
disarm()702 void Select::disarm()
703 {
704 AbstractTool::disarm();
705 resetBoxes();
706 }
707
mousePressEvent(QMouseEvent * event)708 void Select::mousePressEvent(QMouseEvent * event)
709 {
710 Q_ASSERT(_parent != NULL);
711
712 // We only handle the left mouse button
713 if (event->buttons() != Qt::LeftButton) {
714 AbstractTool::mousePressEvent(event);
715 return;
716 }
717
718 PDFDocumentScene * scene = static_cast<PDFDocumentScene*>(_parent->scene());
719 Q_ASSERT(scene != NULL);
720
721 // get the number of the page the mouse is currently over; if the mouse is
722 // not over any page (e.g., it's between pages), there's nothing left to do
723 // here
724 int pageNum = scene->pageNumAt(_parent->mapToScene(event->pos()));
725 if (pageNum < 0)
726 return;
727
728 PDFPageGraphicsItem * pageGraphicsItem = static_cast<PDFPageGraphicsItem*>(scene->pageAt(pageNum));
729 Q_ASSERT(pageGraphicsItem != NULL);
730
731 // Create the highlight path to visualize selections in the scene
732 // Note: it will be parented to the page it belongs to later on
733 // FIXME: Maybe use PDFDocumentView::addHighlightPath here instead?
734 if (!_highlightPath) {
735 _highlightPath = new QGraphicsPathItem(NULL);
736 _highlightPath->setBrush(QBrush(_highlightColor));
737 _highlightPath->setPen(QPen(Qt::transparent));
738 }
739 // Clear any previous selection
740 _highlightPath->setPath(QPainterPath());
741
742 // Save the starting position (in scene coordinates so we can handle scrolling
743 // etc.)
744 _startPos = _parent->mapToScene(event->pos());
745 // Set the mouse mode. Note that _cursorOverBox is updated dynamically in
746 // mouseMoveEvent()
747 _mouseMode = (_cursorOverBox ? MouseMode_TextSelect : MouseMode_MarqueeSelect);
748
749 if (_mouseMode == MouseMode_MarqueeSelect) {
750 // Create the rubber band widget if it doesn't exist and show it
751 if (!_rubberBand)
752 _rubberBand = new QRubberBand(QRubberBand::Rectangle, _parent->viewport());
753 _rubberBand->setGeometry(QRect(event->pos(), event->pos()));
754 _rubberBand->show();
755 }
756 else if (_mouseMode == MouseMode_TextSelect) {
757 // Find the box the mouse cursor is over
758 QPointF curPdfCoords = pageGraphicsItem->pointScale().inverted().map(pageGraphicsItem->mapFromScene(_parent->mapToScene(event->pos())));
759 for (_startBox = 0; _startBox < _boxes.size() && !_boxes[_startBox].boundingBox.contains(curPdfCoords); ++_startBox) ;
760 // If we didn't find the box, something went wrong; bail out
761 if (_startBox >= _boxes.size())
762 _mouseMode = MouseMode_None;
763 else {
764 // Find the subbox the cursor is over (if any)
765 for (_startSubbox = 0; _startSubbox < _boxes[_startBox].subBoxes.size() && !_boxes[_startBox].subBoxes[_startSubbox].boundingBox.contains(curPdfCoords); ++_startSubbox) ;
766 if (_startSubbox >= _boxes[_startBox].subBoxes.size())
767 _startSubbox = 0;
768 }
769 }
770 }
771
mouseMoveEvent(QMouseEvent * event)772 void Select::mouseMoveEvent(QMouseEvent *event)
773 {
774 Q_ASSERT(_parent != NULL);
775 PDFDocumentScene * scene = static_cast<PDFDocumentScene*>(_parent->scene());
776 Q_ASSERT(scene != NULL);
777 Q_ASSERT(!scene->document().isNull());
778
779 // Check if the mouse cursor is over a page. If not, we bail out and keep the
780 // last "valid" state.
781 int pageNum = scene->pageNumAt(_parent->mapToScene(event->pos()));
782 if (pageNum < 0)
783 return;
784
785 // If we are not currently selecting and the mouse moved to a different page,
786 // reset our boxes data
787 // Note: If we are currently selecting, we tick to the original page
788 // regardless where the mouse is
789 if (_mouseMode == MouseMode_None && pageNum != _pageNum)
790 resetBoxes(pageNum);
791
792 PDFPageGraphicsItem * pageGraphicsItem = static_cast<PDFPageGraphicsItem*>(scene->pageAt(pageNum));
793 Q_ASSERT(pageGraphicsItem != NULL);
794
795 QTransform toView = pageGraphicsItem->pointScale();
796
797 // Boxes are given in pdf units (bp), whereas screen coordinates are given in
798 // (scaled) pixels; here, we transform the screen coordinates, rather than
799 // transforming each box
800 QPointF curPdfCoords = pageGraphicsItem->pointScale().inverted().map(pageGraphicsItem->mapFromScene(_parent->mapToScene(event->pos())));
801
802 switch (_mouseMode) {
803 case MouseMode_None:
804 default:
805 {
806 // Check if the cursor is over a box (in which case we use text select mode)
807 // or not (in which case we use marquee select mode)
808 _cursorOverBox = false;
809 foreach(Backend::Page::Box b, _boxes) {
810 if (b.boundingBox.contains(curPdfCoords)) {
811 _cursorOverBox = true;
812 break;
813 }
814 }
815 _parent->viewport()->setCursor(_cursorOverBox ? Qt::IBeamCursor : Qt::CrossCursor);
816 break;
817 }
818 case MouseMode_MarqueeSelect:
819 {
820 if (!_highlightPath || _boxes.size() == 0)
821 break;
822 if (_rubberBand)
823 _rubberBand->setGeometry(QRect(_parent->mapFromScene(_startPos), event->pos()));
824 // Get the selection rect in pdf coords (bp)
825 QPointF startPdfCoords = pageGraphicsItem->pointScale().inverted().map(pageGraphicsItem->mapFromScene(_startPos));
826 QRectF marqueeRect(startPdfCoords, curPdfCoords);
827 QPainterPath highlightPath;
828 // Set WindingFill so overlapping, individual paths are both filled
829 // completely.
830 highlightPath.setFillRule(Qt::WindingFill);
831 foreach(Backend::Page::Box b, _boxes) {
832 // Note: If b.boundingBox is fully contained in the marqueeRect, add it
833 // without iterating over the subboxes. Otherwise, add all intersected
834 // subboxes
835 if (marqueeRect.intersects(b.boundingBox)) {
836 if (b.subBoxes.isEmpty() || marqueeRect.contains(b.boundingBox))
837 highlightPath.addRect(toView.mapRect(b.boundingBox));
838 else {
839 foreach(Backend::Page::Box sb, b.subBoxes) {
840 if (marqueeRect.intersects(sb.boundingBox))
841 highlightPath.addRect(toView.mapRect(sb.boundingBox));
842 }
843 }
844 }
845 }
846 _highlightPath->setPath(highlightPath);
847 _highlightPath->setParentItem(pageGraphicsItem);
848 break;
849 }
850 case MouseMode_TextSelect:
851 {
852 if (!_highlightPath || _boxes.size() == 0)
853 break;
854
855 // Find the box (and subbox therein) that is closest to the current mouse
856 // position
857 int i, j, endBox, endSubbox;
858 double minDist = -1;
859 for (i = 0; i < _boxes.size(); ++i) {
860 double dist = distanceFromRect(curPdfCoords, _boxes[i].boundingBox);
861 if (minDist < -.5 || dist < minDist) {
862 endBox = i;
863 minDist = dist;
864 }
865 }
866 minDist = -1;
867 endSubbox = 0;
868 for (i = 0; i < _boxes[endBox].subBoxes.size(); ++i) {
869 double dist = distanceFromRect(curPdfCoords, _boxes[endBox].subBoxes[i].boundingBox);
870 if (minDist < -.5 || dist < minDist) {
871 endSubbox = i;
872 minDist = dist;
873 }
874 }
875
876 // Ensure startBox <= endBox and (startSubbox <= endSubbox in case of
877 // equality)
878 int startBox = _startBox;
879 int startSubbox = _startSubbox;
880 if (startBox > endBox) {
881 startBox = endBox;
882 startSubbox = endSubbox;
883 endBox = _startBox;
884 endSubbox = _startSubbox;
885 }
886 else if (startBox == endBox && startSubbox > endSubbox) {
887 startSubbox = endSubbox;
888 endSubbox = _startSubbox;
889 }
890
891 QPainterPath highlightPath;
892 // Set WindingFill so overlapping, individual paths are both filled
893 // completely.
894 highlightPath.setFillRule(Qt::WindingFill);
895 for (i = startBox; i <= endBox; ++i) {
896 // Iterate over subboxes in the case that not the whole box might be
897 // selected
898 if ((i == startBox || i == endBox) && _boxes[i].subBoxes.size() > 0) {
899 for (j = 0; j < _boxes[i].subBoxes.size(); ++j) {
900 if ((i == startBox && j < startSubbox) || (i == endBox && j > endSubbox))
901 continue;
902 highlightPath.addRect(toView.mapRect(_boxes[i].subBoxes[j].boundingBox));
903 }
904 }
905 else
906 highlightPath.addRect(toView.mapRect(_boxes[i].boundingBox));
907 }
908 _highlightPath->setPath(highlightPath);
909 _highlightPath->setParentItem(pageGraphicsItem);
910 break;
911 }
912 }
913 }
914
mouseReleaseEvent(QMouseEvent * event)915 void Select::mouseReleaseEvent(QMouseEvent * event)
916 {
917 // We only handle the left mouse button
918 if (event->buttons() != Qt::NoButton || event->button() != Qt::LeftButton) {
919 AbstractTool::mouseReleaseEvent(event);
920 return;
921 }
922 _mouseMode = MouseMode_None;
923 if (_rubberBand)
924 _rubberBand->hide();
925 }
926
keyPressEvent(QKeyEvent * event)927 void Select::keyPressEvent(QKeyEvent *event)
928 {
929 Q_ASSERT(event != NULL);
930
931 if (event->matches(QKeySequence::Copy) && _highlightPath) {
932 // We only handle "copy" (Ctrl+C) here
933 if (!_highlightPath->path().isEmpty()) {
934 Q_ASSERT(_parent != NULL);
935 PDFDocumentScene * scene = static_cast<PDFDocumentScene*>(_parent->scene());
936 Q_ASSERT(scene != NULL);
937 QSharedPointer<Backend::Document> doc(scene->document().toStrongRef());
938 if (!doc)
939 return;
940 if (doc->permissions().testFlag(Backend::Document::Permission_Extract)) {
941 // We only copy text if we are allowed to do so
942
943 QSharedPointer<Backend::Page> page(doc->page(_pageNum).toStrongRef());
944 if (page.isNull())
945 return;
946
947 PDFPageGraphicsItem * pageGraphicsItem = static_cast<PDFPageGraphicsItem*>(scene->pageAt(_pageNum));
948 Q_ASSERT(pageGraphicsItem != NULL);
949
950 QTransform fromView = pageGraphicsItem->pointScale().inverted();
951 QString textToCopy = page->selectedText(_highlightPath->path().toFillPolygons(fromView), NULL, NULL, true);
952 // If the text is empty (e.g., there is no valid selection or the backend
953 // doesn't (properly) support selectedText()) we don't overwrite the
954 // clipboard
955 if (!textToCopy.isEmpty()) {
956 Q_ASSERT(QApplication::clipboard() != NULL);
957 QApplication::clipboard()->setText(textToCopy);
958 }
959 }
960 else {
961 // Inform the user that extracting text is not allowed
962 // TODO: Add hint to unlock document w/ password, once we allow to
963 // provide a password to an unlocked document (i.e., one which we can
964 // display, but for which we don't have author's privileges)
965 QMessageBox::information(_parent, ::QtPDF::PDFDocumentView::trUtf8("Insufficient permission"), ::QtPDF::PDFDocumentView::trUtf8("Text extraction is not allowed for this document."));
966 }
967 }
968 }
969 }
970
resetBoxes(const int pageNum)971 void Select::resetBoxes(const int pageNum /* = -1 */)
972 {
973 _pageNum = pageNum;
974 _boxes.clear();
975 #ifdef DEBUG
976 // In debug builds, remove any previously shown (selectable) boxes
977 foreach(QGraphicsRectItem * rectItem, _displayBoxes) {
978 if (!rectItem)
979 continue;
980 delete rectItem;
981 }
982 _displayBoxes.clear();
983 #endif
984
985 Q_ASSERT(_parent != NULL);
986 PDFDocumentScene * scene = static_cast<PDFDocumentScene*>(_parent->scene());
987 Q_ASSERT(scene != NULL);
988 QSharedPointer<Backend::Document> doc(scene->document().toStrongRef());
989 if (!doc)
990 return;
991
992 QSharedPointer<Backend::Page> page(doc->page(pageNum).toStrongRef());
993 if (page.isNull())
994 return;
995
996 PDFPageGraphicsItem * pageGraphicsItem = static_cast<PDFPageGraphicsItem*>(scene->pageAt(pageNum));
997 Q_ASSERT(pageGraphicsItem != NULL);
998
999 _boxes = page->boxes();
1000 #ifdef DEBUG
1001 // In debug builds, show all selectable boxes
1002 QTransform toView = pageGraphicsItem->pointScale();
1003 foreach(Backend::Page::Box b, _boxes) {
1004 QGraphicsRectItem * rectItem;
1005 if (b.subBoxes.isEmpty()) {
1006 rectItem = scene->addRect(toView.mapRect(b.boundingBox), QPen(_highlightColor));
1007 rectItem->setParentItem(pageGraphicsItem);
1008 _displayBoxes << rectItem;
1009 }
1010 else {
1011 foreach(Backend::Page::Box sb, b.subBoxes) {
1012 rectItem = scene->addRect(toView.mapRect(sb.boundingBox), QPen(_highlightColor));
1013 rectItem->setParentItem(pageGraphicsItem);
1014 _displayBoxes << rectItem;
1015 }
1016 }
1017 }
1018 #endif // DEBUG
1019 }
1020
pageDestroyed()1021 void Select::pageDestroyed()
1022 {
1023 _highlightPath = NULL;
1024 #ifdef DEBUG
1025 _displayBoxes.clear();
1026 #endif
1027 resetBoxes(-1);
1028 }
1029
setHighlightColor(const QColor & color)1030 void Select::setHighlightColor(const QColor & color)
1031 {
1032 _highlightColor = color;
1033 if (_highlightPath)
1034 _highlightPath->setBrush(color);
1035
1036 #ifdef DEBUG
1037 // In debug builds, update the display of selectable boxes
1038 foreach (QGraphicsRectItem * b, _displayBoxes) {
1039 if (!b)
1040 continue;
1041 b->setPen(color);
1042 }
1043 #endif
1044 }
1045
1046 } // namespace DocumentTool
1047 } // namespace QtPDF
1048
1049 // vim: set sw=2 ts=2 et
1050
1051