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