// -------------------------------------------------------------------- // IpeCanvas // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipepage.h" #include "ipestyle.h" #include "ipecanvas.h" #include "ipeoverlay.h" #include "ipefonts.h" #include #include inline QPoint QPt(const IpeVector &v) { return QPoint(int(v.iX), int(v.iY)); } inline IpeString IpeQ(const QString &str) { return IpeString(str.utf8()); } inline QString QIpe(const IpeString &str) { return QString::fromUtf8(str.CString()); } // -------------------------------------------------------------------- /*! \class IpeCanvas \brief A widget that displays an Ipe document page. */ //! Construct a new canvas. IpeCanvas::IpeCanvas(IpeCanvasServices *services, QStatusBar *statusBar, QWidget* parent, WFlags f) : QWidget(parent, "canvas", f), iServices(services), iStatusBar(statusBar) { assert(iServices); setBackgroundMode(NoBackground); iPage = 0; iPan = IpeVector::Zero; iZoom = 1.0; iDimmed = false; iDoubleBuffer = false; iOffscreen.resize(size()); iRepaintObjects = false; iOverlay = 0; iFonts = 0; setMouseTracking(true); iAutoSnap = false; iFifiVisible = false; setFocusPolicy(NoFocus); } //! IpeCanvas destructor. IpeCanvas::~IpeCanvas() { delete iOverlay; delete iFonts; } // -------------------------------------------------------------------- void IpeCanvas::SetFontPool(bool antiAlias, const IpeFontPool *fontPool) { delete iFonts; iFonts = IpeCanvasFonts::New(antiAlias, fontPool, iServices); } // -------------------------------------------------------------------- //! Set the page to be displayed. /*! Doesn't take ownership of any argument. */ void IpeCanvas::SetPage(const IpePage *page, int view, const IpeStyleSheet *sheet, const IpeRect &media, IpeAttribute pageColor) { iPage = page; iView = view; iStyleSheet = sheet; iMediaBox = media; iPageColor = pageColor; delete iOverlay; iOverlay = 0; Update(); } //! Set current pan position. /*! The pan position is the user coordinate that is displayed at the very center of the canvas. */ void IpeCanvas::SetPan(const IpeVector &v) { iPan = v; Update(); } //! Set current zoom factor. /*! The zoom factor maps user coordinates to screen pixel coordinates. */ void IpeCanvas::SetZoom(IpeScalar zoom) { iZoom = zoom; Update(); } //! Set the snapping information. void IpeCanvas::SetSnap(const IpeSnapData &s) { iSnapData = s; } //! Dim whole canvas, except for the overlay. /*! This mode will be reset when the overlay finishes. */ void IpeCanvas::SetDimmed(bool dimmed) { iDimmed = dimmed; Update(); } //! Set double buffering for overlay. /*! This mode will be reset when the overlay finishes. */ void IpeCanvas::SetDoubleBuffer(bool db) { iDoubleBuffer = db; } //! Enable automatic angular snapping with this origin. /*! Snapping is disabled automatically on FinishOverlay(). */ void IpeCanvas::SetAutoOrigin(const IpeVector &v) { iAutoOrigin = v; iAutoSnap = true; } //! Mark for update with redrawing of objects. void IpeCanvas::Update() { iRepaintObjects = true; update(); } //! Mark for update with redrawing of overlay only. void IpeCanvas::UpdateOverlay() { update(); } //! Clear status bar. void IpeCanvas::ClearMessage() { iStatusBar->clear(); } //! Display a message in the status bar. void IpeCanvas::Message(QString str) { iStatusBar->message(str); } IpeVector IpeCanvas::DevToUser(const IpeVector &arg) const { IpeVector v = arg - Center(); v.iX /= iZoom; v.iY /= -iZoom; v += iPan; return v; } IpeVector IpeCanvas::UserToDev(const IpeVector &arg) const { IpeVector v = arg - iPan; v.iX *= iZoom; v.iY *= -iZoom; v += Center(); return v; } //! Adjust painter for canvas coordinate system. IpeMatrix IpeCanvas::CanvasTfm() const { return IpeMatrix(Center()) * IpeLinear(iZoom, 0, 0, -iZoom) * IpeMatrix(-iPan); } // -------------------------------------------------------------------- void IpeCanvas::SetOverlay(IpeOverlay *overlay) { assert(overlay); iOverlay = overlay; iServices->CvSvcSetDrawingMode(true); UpdateOverlay(); } //! Current overlay has done its job. /*! Overlay is deleted, canvas fully updated, and cursor reset. */ void IpeCanvas::FinishOverlay() { delete iOverlay; iOverlay = 0; iDimmed = false; iAutoSnap = false; iDoubleBuffer = false; Update(); unsetCursor(); iServices->CvSvcSetDrawingMode(false); ClearMessage(); } /*! Stores the mouse position in iUnsnappedMousePos, computes Fifi if snapping is enabled, and stores snapped position in iMousePos. */ void IpeCanvas::ComputeFifi(QMouseEvent *ev) { iUnsnappedMousePos = DevToUser(IpeVector(ev->x(), ev->y())); iMousePos = iUnsnappedMousePos; int mask = iAutoSnap ? 0 : IpeSnapData::ESnapAuto; if (iSnapData.iSnap & ~mask) { iSnapData.Snap(iMousePos, iPage, iSnapData.iSnapDistance / iZoom, (iAutoSnap ? &iAutoOrigin : 0)); // convert fifi coordinates back into device space QPoint qfifi = QPt(UserToDev(iMousePos)); if (iFifi != qfifi) { // update old and new fifi position update(iFifi.x() - 8, iFifi.y() - 8, 17, 17); iFifi = qfifi; update(iFifi.x() - 8, iFifi.y() - 8, 17, 17); } } else if (iFifiVisible) { iFifiVisible = false; update(iFifi.x() - 8, iFifi.y() - 8, 17, 17); } iServices->CvSvcMousePosition(iMousePos); } // -------------------------------------------------------------------- void IpeCanvas::DrawAxes(QPainter &qPainter) { IpeMatrix m = CanvasTfm() * IpeMatrix(iSnapData.iOrigin); qPainter.setPen(Qt::green); int x = int(m.Translation().iX); int y = int(m.Translation().iY); double alpha = 0.0; IpeVector ep((width() + height()) / iZoom, 0); while (alpha < IpeTwoPi) { double beta = iSnapData.iDir + alpha; qPainter.moveTo(x, y); qPainter.lineTo(QPt(m * IpeLinear(beta) * ep)); alpha += iSnapData.iAngleSize; } if (iSnapData.iAngleSize > (0.9 * IpePi)) qPainter.drawEllipse(x - 2, y - 2, 4, 4); } void IpeCanvas::DrawGrid(QPainter &qPainter) { double step = iSnapData.iGridSize; double pixstep = step * iZoom; if (pixstep < 2.0) return; int vfactor = 8; if (pixstep > 4.0) vfactor = 4; if (pixstep > 8.0) vfactor = 2; if (pixstep > 16.0) vfactor = 1; double vstep = step * vfactor; IpeVector ll = iMediaBox.Min(); IpeVector ur = iMediaBox.Max(); // grid should remain inside the paper ll.iX = step * int(ll.iX / step); ll.iY = step * int(ll.iY / step); if (ll.iX < iMediaBox.Min().iX - 1.0) ll.iX += step; if (ll.iY < iMediaBox.Min().iY - 1.0) ll.iY += step; IpeMatrix m = CanvasTfm(); IpeVector v; qPainter.setPen(Qt::black); for (v.iX = ll.iX; v.iX < ur.iX; v.iX += vstep) for (v.iY = ll.iY; v.iY < ur.iY; v.iY += step) qPainter.drawPoint(QPt(m * v)); if (vfactor != 1) { for (v.iY = ll.iY; v.iY < ur.iY; v.iY += vstep) for (v.iX = ll.iX; v.iX < ur.iX; v.iX += step) qPainter.drawPoint(QPt(m * v)); } } void IpeCanvas::RepaintObjects() { iOffscreen.fill(Qt::lightGray); iRepaintObjects = false; if (!iPage) return; QPainter qPainter; qPainter.begin(&iOffscreen); IpeCanvasPainter painter(iStyleSheet, iFonts, &iOffscreen, &qPainter, iZoom, false, 200); painter.Transform(CanvasTfm()); painter.Push(); painter.SetStroke(IpeAttribute::Void()); painter.SetFill(iPageColor); painter.NewPath(); painter.Rect(iMediaBox); painter.DrawPath(); painter.Pop(); painter.SetDimmed(iDimmed); if (iSnapData.iGridVisible) DrawGrid(qPainter); if (iSnapData.iWithAxes) DrawAxes(qPainter); painter.Push(); std::vector layerVisible; iPage->MakeLayerTable(layerVisible, iView, false); for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) { if (layerVisible[it->Layer()]) { painter.SetDimmed(iDimmed || iPage->Layer(it->Layer()).IsDimmed()); it->Object()->Draw(painter); } } painter.Pop(); qPainter.end(); } // -------------------------------------------------------------------- void IpeCanvas::paintEvent(QPaintEvent *ev) { if (iRepaintObjects) RepaintObjects(); QPixmap buffer; QPainter qPainter; if (iDoubleBuffer) { buffer.resize(size()); bitBlt(&buffer, 0, 0, &iOffscreen, 0, 0, iOffscreen.width(), iOffscreen.height()); qPainter.begin(&buffer); } else { #if 0 bitBlt(this, 0, 0, &iOffscreen, 0, 0, iOffscreen.width(), iOffscreen.height()); qPainter.begin(this); #else // bitBlt cannot be used, because the qPainter is clipped // to the area needing to qPainter.begin(this); QRect r = ev->rect(); qPainter.drawPixmap(r.left(), r.top(), iOffscreen, r.left(), r.top(), r.width(), r.height()); #endif } if (iOverlay) { iOverlay->Draw(ev, &qPainter); } else { IpeOverlayPainter painter(iStyleSheet, &qPainter); painter.Transform(CanvasTfm()); QPen primaryPen; QPen secondaryPen; primaryPen.setColor(Qt::red); primaryPen.setWidth(2); secondaryPen.setColor(QColor("purple")); painter.Push(); for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) { if (it->Select() == IpePgObject::EPrimary) { qPainter.setPen(primaryPen); it->Object()->Draw(painter); } else if (it->Select() == IpePgObject::ESecondary) { qPainter.setPen(secondaryPen); it->Object()->Draw(painter); } } painter.Pop(); } if (iSnapData.iSnap) { qPainter.setPen(Qt::red); qPainter.drawLine(iFifi.x() - 8, iFifi.y(), iFifi.x() + 8, iFifi.y()); qPainter.drawLine(iFifi.x(), iFifi.y() - 8, iFifi.x(), iFifi.y() + 8); iFifiVisible = true; } qPainter.end(); if (iDoubleBuffer) bitBlt(this, 0, 0, &buffer, 0, 0, buffer.width(), buffer.height()); } void IpeCanvas::resizeEvent(QResizeEvent *) { iOffscreen.resize(size()); iRepaintObjects = true; } void IpeCanvas::wheelEvent(QWheelEvent *ev) { iServices->CvSvcWheelZoom(ev->delta()); ev->accept(); } void IpeCanvas::mousePressEvent(QMouseEvent *ev) { ClearMessage(); ComputeFifi(ev); if (iOverlay) { iOverlay->MousePress(ev); } else { // iServices will call SetOverlay iServices->CvSvcRequestOverlay(ev); } } void IpeCanvas::mouseReleaseEvent(QMouseEvent *ev) { ComputeFifi(ev); if (iOverlay) iOverlay->MouseRelease(ev); } void IpeCanvas::mouseMoveEvent(QMouseEvent *ev) { ComputeFifi(ev); if (iOverlay) iOverlay->MouseMove(ev); } QSize IpeCanvas::sizeHint() const { return QSize(640, 480); } // --------------------------------------------------------------------