1
// --------------------------------------------------------------------
3
// --------------------------------------------------------------------
6
This file is part of the extensible drawing editor Ipe.
7
Copyright (C) 1993-2009 Otfried Cheong
9
Ipe is free software; you can redistribute it and/or modify it
10
under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 3 of the License, or
12
(at your option) any later version.
14
As a special exception, you have permission to link Ipe with the
15
CGAL library and distribute executables, as long as you follow the
16
requirements of the Gnu General Public License in regard to all of
17
the software in the executable aside from CGAL.
19
Ipe is distributed in the hope that it will be useful, but WITHOUT
20
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
22
License for more details.
24
You should have received a copy of the GNU General Public License
25
along with Ipe; if not, you can find it at
26
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
27
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31
#include "ipeqtcanvas.h"
34
#include "ipecairopainter.h"
37
#include <QPaintEvent>
40
using namespace ipeqt;
42
// --------------------------------------------------------------------
45
\brief Ipe canvas library namespace
47
This namespace is used for the components of Ipe that are dependent
51
/*! \defgroup qtcanvas Ipe Qt canvas
52
\brief A Qt widget that displays an Ipe document page.
54
This module contains the classes needed to display and edit Ipe
55
objects using the Qt toolkit.
57
These classes are not in Ipelib, but in a separate library
61
// --------------------------------------------------------------------
63
class IpeQtPainter : public Painter {
65
IpeQtPainter(const Cascade *sheet, QPainter *painter);
66
virtual ~IpeQtPainter();
69
virtual void doNewPath();
70
virtual void doMoveTo(const Vector &v);
71
virtual void doLineTo(const Vector &v);
72
virtual void doCurveTo(const Vector &v1, const Vector &v2,
74
virtual void doClosePath();
76
virtual void doDrawPath(TPathMode mode);
83
IpeQtPainter::IpeQtPainter(const Cascade *sheet, QPainter *painter)
89
IpeQtPainter::~IpeQtPainter()
94
void IpeQtPainter::doNewPath()
99
void IpeQtPainter::doMoveTo(const Vector &v)
104
void IpeQtPainter::doLineTo(const Vector &v)
109
void IpeQtPainter::doCurveTo(const Vector &v1, const Vector &v2,
112
iPP.cubicTo(QPt(v1), QPt(v2), QPt(v3));
115
void IpeQtPainter::doClosePath()
120
void IpeQtPainter::doDrawPath(TPathMode mode)
122
if (mode >= EStrokedAndFilled) {
123
QBrush qbrush(QIpe(fill()));
124
iQP->fillPath(iPP, qbrush);
126
if (mode <= EStrokedAndFilled) {
127
QPen qpen(QIpe(stroke()));
128
qpen.setWidthF(pen().toDouble());
129
iQP->strokePath(iPP, qpen);
133
// --------------------------------------------------------------------
135
/*! \class ipeqt::Canvas
137
\brief A Qt widget that displays an Ipe document page.
140
//! Construct a new canvas.
141
Canvas::Canvas(QWidget* parent, Qt::WFlags f)
144
setAttribute(Qt::WA_NoBackground);
145
setMouseTracking(true);
146
setFocusPolicy(Qt::ClickFocus);
155
iWidth = 0; // not yet known (canvas is not yet mapped)
157
iRepaintObjects = false;
160
iFifiVisible = false;
162
iStyle.paperColor = Color(1000,1000,1000);
163
iStyle.pretty = false;
164
iStyle.classicGrid = false;
165
iStyle.thinLine = 0.2;
166
iStyle.thickLine = 0.9;
169
iSnap.iGridVisible = false;
171
iSnap.iAngleSize = M_PI / 6.0;
172
iSnap.iSnapDistance = 10;
173
iSnap.iWithAxes = false;
174
iSnap.iOrigin = Vector::ZERO;
182
cairo_surface_destroy(iSurface);
187
QSize Canvas::sizeHint() const
189
return QSize(640, 480);
192
// --------------------------------------------------------------------
194
//! set information about Latex fonts (from ipe::Document)
195
void Canvas::setFontPool(const FontPool *fontPool)
198
iFonts = Fonts::New(fontPool);
201
// --------------------------------------------------------------------
203
//! Set the page to be displayed.
204
/*! Doesn't take ownership of any argument. */
205
void Canvas::setPage(const Page *page, int view, const Cascade *sheet)
212
//! Set style of canvas drawing.
213
/*! Includes paper color, pretty text, and grid. */
214
void Canvas::setCanvasStyle(const Style &style)
219
//! Determine whether pretty display should be used.
220
/*! In pretty display, no dashed lines are drawn around text objects,
221
and if Latex font data is not available, no text is drawn at all. */
222
void Canvas::setPretty(bool pretty)
224
iStyle.pretty = pretty;
227
//! Set current pan position.
228
/*! The pan position is the user coordinate that is displayed at
229
the very center of the canvas. */
230
void Canvas::setPan(const Vector &v)
235
//! Set current zoom factor.
236
/*! The zoom factor maps user coordinates to screen pixel coordinates. */
237
void Canvas::setZoom(double zoom)
242
//! Set the snapping information.
243
void Canvas::setSnap(const Snap &s)
248
//! Dim whole canvas, except for the Tool.
249
/*! This mode will be reset when the Tool finishes. */
250
void Canvas::setDimmed(bool dimmed)
255
//! Enable automatic angular snapping with this origin.
256
void Canvas::setAutoOrigin(const Vector &v)
262
//! Mark for update with redrawing of objects.
263
void Canvas::update()
265
iRepaintObjects = true;
269
//! Mark for update with redrawing of tool only.
270
void Canvas::updateTool()
275
//! Convert canvas (device) coordinates to user coordinates.
276
Vector Canvas::devToUser(const Vector &arg) const
278
Vector v = arg - center();
285
//! Convert user coordinates to canvas (device) coordinates.
286
Vector Canvas::userToDev(const Vector &arg) const
288
Vector v = arg - iPan;
295
//! Matrix mapping user coordinates to canvas coordinates
296
Matrix Canvas::canvasTfm() const
298
return Matrix(center()) *
299
Linear(iZoom, 0, 0, -iZoom) *
303
// --------------------------------------------------------------------
305
void Canvas::drawAxes(cairo_t *cc)
308
double ep = (iWidth + iHeight) / iZoom;
311
cairo_set_source_rgb(cc, 0.0, 1.0, 0.0);
312
cairo_set_line_width(cc, 2.0 / iZoom);
313
while (alpha < IpeTwoPi) {
314
double beta = iSnap.iDir + alpha;
315
cairo_move_to(cc, iSnap.iOrigin.x, iSnap.iOrigin.y);
317
cairo_rel_line_to(cc, ep * dir.x, ep * dir.y);
320
cairo_set_line_width(cc, 1.0 / iZoom);
322
alpha += iSnap.iAngleSize;
328
void Canvas::drawGrid(cairo_t *cc)
330
int step = iSnap.iGridSize;
331
double pixstep = step * iZoom;
336
int vstep = step * vfactor;
338
Rect paper = iCascade->findLayout()->paper();
339
Vector ll = paper.bottomLeft();
340
Vector ur = paper.topRight();
342
int left = step * int(ll.x / step);
345
int bottom = step * int(ll.y / step);
349
// only draw lines that intersect canvas
350
Vector screenUL = devToUser(Vector::ZERO);
351
Vector screenLR = devToUser(Vector(iWidth, iHeight));
354
cairo_set_source_rgb(cc, 0.3, 0.3, 0.3);
356
if (iStyle.classicGrid) {
357
double lw = iStyle.thinLine / iZoom;
358
cairo_set_line_width(cc, lw);
359
for (int y = bottom; y < ur.y; y += vstep) {
360
if (screenLR.y <= y && y <= screenUL.y) {
361
for (int x = left; x < ur.x; x += vstep) {
362
if (screenUL.x <= x && x <= screenLR.x) {
363
cairo_move_to(cc, x, y - 0.5 * lw);
364
cairo_line_to(cc, x, y + 0.5 * lw);
371
double thinLine = iStyle.thinLine / iZoom;
372
double thickLine = iStyle.thickLine / iZoom;
373
int thickStep = 4 * step;
375
// draw horizontal lines
376
for (int y = bottom; y < ur.y; y += vstep) {
377
if (screenLR.y <= y && y <= screenUL.y) {
378
cairo_set_line_width(cc, (y % thickStep) ? thinLine : thickLine);
379
cairo_move_to(cc, ll.x, y);
380
cairo_line_to(cc, ur.x, y);
385
// draw vertical lines
386
for (int x = left; x < ur.x; x += vstep) {
387
if (screenUL.x <= x && x <= screenLR.x) {
388
cairo_set_line_width(cc, (x % thickStep) ? thinLine : thickLine);
389
cairo_move_to(cc, x, ll.y);
390
cairo_line_to(cc, x, ur.y);
399
void Canvas::drawPaper(cairo_t *cc)
401
const Layout *l = iCascade->findLayout();
402
cairo_rectangle(cc, -l->iOrigin.x, -l->iOrigin.y,
403
l->iPaperSize.x, l->iPaperSize.y);
404
cairo_set_source_rgb(cc, iStyle.paperColor.iRed.toDouble(),
405
iStyle.paperColor.iGreen.toDouble(),
406
iStyle.paperColor.iBlue.toDouble());
410
void Canvas::drawFrame(cairo_t *cc)
412
const Layout *l = iCascade->findLayout();
413
cairo_set_source_rgb(cc, 0.5, 0.5, 0.5);
415
double dashes[2] = {3.0 / iZoom, 7.0 / iZoom};
416
cairo_set_dash(cc, dashes, 2, 0.0);
417
cairo_set_line_width(cc, 2.5 / iZoom);
418
cairo_move_to(cc, 0.0, 0.0);
419
cairo_line_to(cc, 0.0, l->iFrameSize.y);
420
cairo_line_to(cc, l->iFrameSize.x, l->iFrameSize.y);
421
cairo_line_to(cc, l->iFrameSize.x, 0);
422
cairo_close_path(cc);
427
void Canvas::drawObjects(cairo_t *cc)
432
CairoPainter painter(iCascade, iFonts, cc, iZoom, iStyle.pretty);
433
painter.setDimmed(iDimmed);
434
// painter.Transform(CanvasTfm());
435
painter.pushMatrix();
437
const Symbol *background =
438
iCascade->findSymbol(Attribute::BACKGROUND());
439
if (background && iPage->findLayer("BACKGROUND") < 0)
440
background->iObject->draw(painter);
442
const Text *title = iPage->titleText();
444
title->draw(painter);
446
for (int i = 0; i < iPage->count(); ++i) {
447
if (iPage->objectVisible(iView, i))
448
iPage->object(i)->draw(painter);
453
// --------------------------------------------------------------------
455
//! Draw the current canvas tool.
456
/*! If no tool is set, it draws the selected objects. */
457
void Canvas::drawTool(Painter &painter)
460
iTool->draw(painter);
462
for (int i = 0; i < iPage->count(); ++i) {
463
if (iPage->objectVisible(iView, i)) {
464
if (iPage->select(i) == EPrimarySelected) {
465
painter.setStroke(Attribute(Color(1000, 0, 0)));
466
painter.setPen(Attribute(Fixed(2)));
467
iPage->object(i)->drawSimple(painter);
468
} else if (iPage->select(i) == ESecondarySelected) {
469
painter.setStroke(Attribute(Color(1000, 0, 1000)));
470
painter.setPen(Attribute(Fixed(1)));
471
iPage->object(i)->drawSimple(painter);
479
/*! Emits toolChanged() signal. */
480
void Canvas::setTool(Tool *tool)
485
emit toolChanged(true);
488
// Current tool has done its job.
489
/* Tool is deleted, canvas fully updated, and cursor reset.
490
Emits toolChanged() signal. */
491
void Canvas::finishTool()
499
emit toolChanged(false);
502
// --------------------------------------------------------------------
504
bool Canvas::snapToPaperAndFrame()
506
double snapDist = iSnap.iSnapDistance / iZoom;
508
Vector fifi = iMousePos;
509
const Layout *layout = iCascade->findLayout();
510
Rect paper = layout->paper();
511
Rect frame(Vector::ZERO, layout->iFrameSize);
514
if (iSnap.iSnap & Snap::ESnapVtx) {
515
paper.bottomLeft().snap(iMousePos, fifi, d);
516
paper.topRight().snap(iMousePos, fifi, d);
517
paper.topLeft().snap(iMousePos, fifi, d);
518
paper.bottomRight().snap(iMousePos, fifi, d);
519
frame.bottomLeft().snap(iMousePos, fifi, d);
520
frame.topRight().snap(iMousePos, fifi, d);
521
frame.topLeft().snap(iMousePos, fifi, d);
522
frame.bottomRight().snap(iMousePos, fifi, d);
525
// Return if snapping has occurred
532
if (iSnap.iSnap & Snap::ESnapBd) {
533
Segment(paper.bottomLeft(), paper.bottomRight()).snap(iMousePos, fifi, d);
534
Segment(paper.bottomRight(), paper.topRight()).snap(iMousePos, fifi, d);
535
Segment(paper.topRight(), paper.topLeft()).snap(iMousePos, fifi, d);
536
Segment(paper.topLeft(), paper.bottomLeft()).snap(iMousePos, fifi, d);
537
Segment(frame.bottomLeft(), frame.bottomRight()).snap(iMousePos, fifi, d);
538
Segment(frame.bottomRight(), frame.topRight()).snap(iMousePos, fifi, d);
539
Segment(frame.topRight(), frame.topLeft()).snap(iMousePos, fifi, d);
540
Segment(frame.topLeft(), frame.bottomLeft()).snap(iMousePos, fifi, d);
551
// --------------------------------------------------------------------
553
/*! Stores the mouse position in iUnsnappedMousePos, computes Fifi if
554
snapping is enabled, and stores snapped position in iMousePos. */
555
void Canvas::computeFifi(double x, double y)
557
iUnsnappedMousePos = devToUser(Vector(x, y));
558
iMousePos = iUnsnappedMousePos;
563
int mask = iAutoSnap ? 0 : Snap::ESnapAuto;
564
if (iSnap.iSnap & ~mask) {
565
if (!iSnap.snap(iMousePos, iPage, iSnap.iSnapDistance / iZoom,
566
(iAutoSnap ? &iAutoOrigin : 0)))
567
snapToPaperAndFrame();
569
// convert fifi coordinates back into device space
570
Vector fifi = userToDev(iMousePos);
571
if (iFifiVisible && fifi != iOldFifi) {
572
QWidget::update(QRect(int(iOldFifi.x - 10), int(iOldFifi.y - 10),
574
QWidget::update(QRect(int(fifi.x - 10), int(fifi.y - 10), 21, 21));
576
} else if (iFifiVisible) {
578
QWidget::update(QRect(int(iOldFifi.x - 10), int(iOldFifi.y - 10), 21, 21));
579
iFifiVisible = false;
583
//! Set whether Fifi should be shown.
584
/*! Fifi will only be shown if a snapping mode is active. */
585
void Canvas::setFifiVisible(bool visible)
587
iFifiVisible = visible;
589
updateTool(); // when making visible, wait for position update
592
//! Return snapped mouse position without angular snapping.
593
Vector Canvas::simpleSnapPos() const
595
Vector pos = iUnsnappedMousePos;
596
iSnap.simpleSnap(pos, iPage, iSnap.iSnapDistance / iZoom);
600
// --------------------------------------------------------------------
602
void Canvas::keyPressEvent(QKeyEvent *ev)
604
String s = IpeQ(ev->text());
605
if (iTool && iTool->key(ev->key(), ev->modifiers(), s))
611
void Canvas::mousePressEvent(QMouseEvent *ev)
613
iGlobalPos = Vector(ev->globalPos().x(), ev->globalPos().y());
614
computeFifi(ev->x(), ev->y());
616
iTool->mouseButton(ev->button() | ev->modifiers(), true);
618
emit mouseAction(ev->button() | ev->modifiers());
621
void Canvas::mouseReleaseEvent(QMouseEvent *ev)
623
iGlobalPos = Vector(ev->globalPos().x(), ev->globalPos().y());
624
computeFifi(ev->x(), ev->y());
626
iTool->mouseButton(ev->button(), false);
629
void Canvas::mouseMoveEvent(QMouseEvent *ev)
631
computeFifi(ev->x(), ev->y());
633
iTool->mouseMove(ev->buttons());
634
emit positionChanged();
637
void Canvas::tabletEvent(QTabletEvent *ev)
639
Vector globalPos(ev->hiResGlobalPos().x(), ev->hiResGlobalPos().y());
640
QPointF hiPos = ev->hiResGlobalPos() - (ev->globalPos() - ev->pos());
643
switch (ev->type()) {
644
case QEvent::TabletPress:
645
if (ev->pointerType() == QTabletEvent::Eraser)
646
return; // not yet implemented
647
iGlobalPos = globalPos;
648
computeFifi(hiPos.x(), hiPos.y());
650
iTool->mouseButton(Qt::LeftButton, true);
652
emit mouseAction(Qt::LeftButton);
654
case QEvent::TabletMove:
655
if (ev->pressure() > 0.01) {
656
computeFifi(hiPos.x(), hiPos.y());
658
iTool->mouseMove(Qt::LeftButton);
659
emit positionChanged();
662
// else fall through and consider it a release event
663
case QEvent::TabletRelease:
664
iGlobalPos = globalPos;
665
computeFifi(hiPos.x(), hiPos.y());
667
iTool->mouseButton(Qt::LeftButton, false);
670
ipeDebug("Unknown tablet event");
675
void Canvas::wheelEvent(QWheelEvent *ev)
677
emit wheelMoved(ev->delta());
681
void Canvas::paintEvent(QPaintEvent * ev)
684
if (s.width() != iWidth || s.height() != iHeight) {
686
// ipeDebug("size has changed to %d x %d", s.width(), s.height());
688
cairo_surface_destroy(iSurface);
691
iHeight = s.height();
692
iRepaintObjects = true;
694
if (iRepaintObjects) {
695
iRepaintObjects = false;
698
cairo_image_surface_create(CAIRO_FORMAT_RGB24, iWidth, iHeight);
699
cairo_t *cc = cairo_create(iSurface);
701
cairo_set_source_rgb(cc, 0.4, 0.4, 0.4);
702
cairo_rectangle(cc, 0, 0, iWidth, iHeight);
705
cairo_translate(cc, 0.5 * iWidth, 0.5 * iHeight);
706
cairo_scale(cc, iZoom, -iZoom);
707
cairo_translate(cc, -iPan.x, -iPan.y);
713
if (iSnap.iGridVisible)
719
cairo_surface_flush(iSurface);
722
// -----------------------------------
724
qPainter.begin(this);
725
QRect r = ev->rect();
726
QImage bits(cairo_image_surface_get_data(iSurface),
727
iWidth, iHeight, QImage::Format_RGB32);
728
qPainter.drawImage(r.left(), r.top(), bits,
729
r.left(), r.top(), r.width(), r.height());
730
qPainter.translate(-1, -1);
732
Vector p = userToDev(iMousePos);
733
qPainter.setPen(QColor(255, 0, 0, 255));
734
qPainter.drawLine(QPt(p - Vector(8, 0)), QPt(p + Vector(8, 0)));
735
qPainter.drawLine(QPt(p - Vector(0, 8)), QPt(p + Vector(0, 8)));
738
// -----------------------------------
740
IpeQtPainter qp(iCascade, &qPainter);
741
qp.transform(canvasTfm());
749
// --------------------------------------------------------------------