// -------------------------------------------------------------------- // Overlay for the canvas --- shows moving objects etc. // -------------------------------------------------------------------- /* 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 "ipetext.h" #include "ipepage.h" #include "ipeundo.h" #include "ipeoverlay.h" #include "ipecanvas.h" #include #include #include #include inline QPoint QPt(const IpeVector &v) { return QPoint(int(v.iX), int(v.iY)); } // -------------------------------------------------------------------- /*! \class IpeOverlayPainter \brief IpePainter for painting into an overlay. Draws a simplified presentation of objects. */ /*! Needs IpeStyleSheet only to look up IpeReference objects. */ IpeOverlayPainter::IpeOverlayPainter(const IpeStyleSheet *sheet, QPainter *painter) : IpePainter(sheet), iPainter(painter) { iPainter->setFont(QApplication::font()); } void IpeOverlayPainter::DoMoveTo(const IpeVector &v) { iPos = QPt(Matrix() * v); iPainter->moveTo(iPos); iPathBegin = iPos; } void IpeOverlayPainter::DoLineTo(const IpeVector &v) { iPos = QPt(Matrix() * v); iPainter->lineTo(iPos); } void IpeOverlayPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2, const IpeVector &v3) { QPointArray arr(4); arr.setPoint(0, iPos); arr.setPoint(1, QPt(Matrix() * v1)); arr.setPoint(2, QPt(Matrix() * v2)); iPos = QPt(Matrix() * v3); arr.setPoint(3, iPos); #if QT_VERSION >= 0x030000 iPainter->drawCubicBezier(arr); #else iPainter->drawQuadBezier(arr); #endif iPainter->moveTo(iPos); } void IpeOverlayPainter::DoClosePath() { iPainter->lineTo(iPathBegin); } //! Draw outline of bitmap only. void IpeOverlayPainter::DoDrawBitmap(IpeBitmap) { iPainter->moveTo(QPt(Matrix().Translation())); iPainter->lineTo(QPt(Matrix() * IpeVector(1.0, 0.0))); iPainter->lineTo(QPt(Matrix() * IpeVector(1.0, 1.0))); iPainter->lineTo(QPt(Matrix() * IpeVector(0.0, 1.0))); iPainter->lineTo(QPt(Matrix().Translation())); } //! Draw bounding box of text only. void IpeOverlayPainter::DoDrawText(const IpeText *text) { iPainter->moveTo(QPt(Matrix() * IpeVector(0,0))); iPainter->lineTo(QPt(Matrix() * IpeVector(0, text->TotalHeight()))); iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(), text->TotalHeight()))); iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(), 0))); if (text->GetXForm()) iPainter->lineTo(QPt(Matrix() * IpeVector(0,0))); } // -------------------------------------------------------------------- /*! \class IpeOverlay \brief Abstract base class for various modes that draw overlays. The Canvas doesn't know about the various modes for object creation, editing, and moving, but delegates the handling to a subclass of IpeOverlay. The ObjectCreator classes are IpeOverlay's, so are IpeSelecting, IpeTransforming, and IpePanning. */ //! Constructor. IpeOverlay::IpeOverlay(IpeCanvas *canvas) : iCanvas(canvas) { // nothing } //! Virtual destructor. IpeOverlay::~IpeOverlay() { // nothing } //! Handle mouse press event. /*! Default implementation does nothing. */ void IpeOverlay::MousePress(QMouseEvent *) { // nothing } //! Handle mouse release event. /*! Default implementation does nothing. */ void IpeOverlay::MouseRelease(QMouseEvent *) { // nothing } //! Handle mouse move event. /*! Default implementation does nothing. */ void IpeOverlay::MouseMove(QMouseEvent *) { // nothing } //! Handle key press. /*! Default implementation ignores it. */ void IpeOverlay::KeyPress(QKeyEvent *ev) { ev->ignore(); } // -------------------------------------------------------------------- /*! \class IpePanning \brief Canvas panning operation */ //! Constructor starts panning. IpePanning::IpePanning(QMouseEvent *ev, const IpePage *page, IpeCanvas *canvas) : IpeOverlay(canvas) { iPage = page; iMouseDown = IpeVector(ev->x(), -ev->y()); iPan = iCanvas->Pan(); #if QT_VERSION >= 0x030000 iCanvas->setCursor(QCursor(Qt::PointingHandCursor)); #else iCanvas->setCursor(QCursor(PointingHandCursor)); #endif } void IpePanning::Draw(QPaintEvent *, QPainter *qPainter) const { qPainter->setPen(Qt::blue); IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Translate(iCanvas->Center()); painter.Transform(IpeLinear(iCanvas->Zoom(), 0, 0, -iCanvas->Zoom())); painter.Translate(-iPan); painter.NewPath(); painter.Rect(iCanvas->MediaBox()); painter.DrawPath(); painter.Push(); for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) { it->Object()->Draw(painter); } painter.Pop(); } void IpePanning::MouseRelease(QMouseEvent *ev) { IpeVector displ = IpeVector(ev->x(), -ev->y()) - iMouseDown; iPan = iCanvas->Pan() - (1.0/iCanvas->Zoom()) * displ; iCanvas->SetPan(iPan); iCanvas->FinishOverlay(); } void IpePanning::MouseMove(QMouseEvent *ev) { IpeVector displ = IpeVector(ev->x(), -ev->y()) - iMouseDown; iPan = iCanvas->Pan() - (1.0/iCanvas->Zoom()) * displ; iCanvas->UpdateOverlay(); } // -------------------------------------------------------------------- /*! \class IpeSelecting \brief Object selection operation */ class SelectCompare { public: int operator()(const IpeSelecting::SObj &lhs, const IpeSelecting::SObj &rhs) const { return (lhs.iDistance < rhs.iDistance); } }; //! Constructor starts selection. IpeSelecting::IpeSelecting(QMouseEvent *ev, IpePage *page, int view, IpeCanvas *canvas, bool nonDestructive, int selectDistance, IpeOverlayServices *services) : IpeOverlay(canvas), iServices(services) { iPage = page; iNonDestructive = nonDestructive; iSelectDistance = selectDistance; iMouseDown = IpeVector(ev->x(), ev->y()); iDragging = false; iView = view; // coordinates in user space IpeVector v = iCanvas->UnsnappedPos(); double bound = iSelectDistance / iCanvas->Zoom(); // determine visible and unlocked layers std::vector layerVisible; iPage->MakeLayerTable(layerVisible, iView, true); // Collect objects close enough double d; for (IpePage::reverse_iterator it = iPage->rbegin(); it != iPage->rend(); ++it) { if (layerVisible[it->Layer()]) { if ((d = it->Distance(v, bound)) < bound) { SObj obj; obj.iIt = it.base(); --obj.iIt; obj.iDistance = d; iObjs.push_back(obj); } } } iCur = 0; std::stable_sort(iObjs.begin(), iObjs.end(), SelectCompare()); #if QT_VERSION >= 0x030000 iCanvas->setCursor(QCursor(Qt::CrossCursor)); #else iCanvas->setCursor(QCursor(CrossCursor)); #endif } void IpeSelecting::KeyPress(QKeyEvent *ev) { if (!iDragging && ev->key() == Qt::Key_Space && iObjs.size() > 0) { iCur++; if (iCur >= int(iObjs.size())) iCur = 0; iCanvas->UpdateOverlay(); } else ev->ignore(); } void IpeSelecting::Draw(QPaintEvent *, QPainter *qPainter) const { if (iDragging) { qPainter->setPen(Qt::cyan); IpeRect r(iMouseDown, iCorner); qPainter->drawRect(int(r.Min().iX), int(r.Min().iY), int(r.Width()), int(r.Height())); } else { qPainter->setPen(Qt::cyan); qPainter->drawEllipse(int(iMouseDown.iX - iSelectDistance), int(iMouseDown.iY - iSelectDistance), 2 * iSelectDistance, 2 * iSelectDistance); if (iObjs.size() > 0) { // display current object qPainter->setPen(Qt::red); IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Transform(iCanvas->CanvasTfm()); painter.Push(); iObjs[iCur].iIt->Object()->Draw(painter); painter.Pop(); } } } void IpeSelecting::MouseMove(QMouseEvent *ev) { iCorner = IpeVector(ev->x(), ev->y()); if ((iCorner - iMouseDown).SqLen() > 9.0) iDragging = true; iCanvas->UpdateOverlay(); } void IpeSelecting::MouseRelease(QMouseEvent *ev) { bool changed = false; // IpeAutoPtr originalPage = new IpePage(*iPage); if (iDragging) { // select all objects inside the rectangle IpeVector v1 = iMouseDown - iCanvas->Center(); v1.iX /= iCanvas->Zoom(); v1.iY /= -iCanvas->Zoom(); v1 += iCanvas->Pan(); IpeVector v2 = IpeVector(ev->x(), ev->y()) - iCanvas->Center(); v2.iX /= iCanvas->Zoom(); v2.iY /= -iCanvas->Zoom(); v2 += iCanvas->Pan(); // determine visible and unlocked layers std::vector layerVisible; iPage->MakeLayerTable(layerVisible, iView, true); IpeRect r(v1, v2); for (IpePage::iterator it = iPage->begin(); it != iPage->end(); ++it) { if (!iNonDestructive && it->Select()) { changed = true; it->SetSelect(IpePgObject::ENone); } if (layerVisible[it->Layer()] && r.Contains(it->BBox()) && !it->Select()) { changed = true; it->SetSelect(IpePgObject::ESecondary); } } iPage->EnsurePrimarySelection(); } else if (iObjs.size() > 0) { IpePage::iterator it1 = iObjs[iCur].iIt; if (iNonDestructive) { if (!it1->Select()) // selecting unselected object it1->SetSelect(IpePgObject::ESecondary); else // deselecting selected object it1->SetSelect(IpePgObject::ENone); changed = true; iPage->EnsurePrimarySelection(); } else { // destructive: unselect all for (IpePage::iterator it = iPage->begin(); it != iPage->end(); ++it) { if (it != it1 && it->Select()) { changed = true; it->SetSelect(IpePgObject::ENone); } } if (it1->Select() != IpePgObject::EPrimary) changed = true; it1->SetSelect(IpePgObject::EPrimary); } } else { // no object in range, deselect all if (!iNonDestructive) { changed = iPage->HasSelection(); iPage->DeselectAll(); } } /* if (changed) iServices->OvSvcAddUndoItem(originalPage.Take(), QObject::tr("selecting object")); */ iCanvas->FinishOverlay(); } // -------------------------------------------------------------------- /*! \class IpeTransforming \brief Transform the selected objects. Supports moving, rotating, scaling, and stretching. */ //! Constructor starts panning. IpeTransforming::IpeTransforming(QMouseEvent *ev, IpePage *page, int view, IpeCanvas *canvas, TType type, IpeOverlayServices *services) : IpeOverlay(canvas), iPage(page), iView(view), iServices(services), iType(type) { iWithShift = (ev->state() & QMouseEvent::ShiftButton); iMouseDown = iCanvas->Pos(); } /*! Find objects to be transformed. Must be called after construction of IpeTransforming object. If it returns false, there are no suitable objects, and the whole overlay business must be aborted. */ bool IpeTransforming::FindObjects(int selectDistance) { if (!UpdateCloseSelection(selectDistance)) return false; const IpeSnapData &sd = iCanvas->SnapData(); if (sd.iWithAxes) { iOrigin = sd.iOrigin; } else { // find bounding box of selected objects IpeRect bbox; for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) { if (it->Select() != IpePgObject::ENone) { bbox.AddRect(it->BBox()); } } iOrigin = 0.5 * (bbox.Min() + bbox.Max()); if (iType == EStretch || iType == EScale) { if (iMouseDown.iX > iOrigin.iX) iOrigin.iX = bbox.Min().iX; else iOrigin.iX = bbox.Max().iX; if (iMouseDown.iY > iOrigin.iY) iOrigin.iY = bbox.Min().iY; else iOrigin.iY = bbox.Max().iY; } } // iCanvas->setCursor(QCursor(PointingHandCursor)); // begin with identity matrix return true; } /*! If there is a selected object close enough, return true. Otherwise, check whether the closest object is close enough. If so, select it and return true. If not, return whether the page has a selection at all. */ bool IpeTransforming::UpdateCloseSelection(int selectDistance) { double d = selectDistance / iCanvas->Zoom(); return iPage->UpdateCloseSelection(iMouseDown, d, false, iView); } void IpeTransforming::Draw(QPaintEvent *, QPainter *qPainter) const { qPainter->setPen(Qt::darkGreen); IpeOverlayPainter painter(iCanvas->StyleSheet(), qPainter); painter.Transform(iCanvas->CanvasTfm()); painter.Push(); painter.Transform(iTransform); for (IpePage::const_iterator it = iPage->begin(); it != iPage->end(); ++it) { if (it->Select()) it->Object()->Draw(painter); } painter.Pop(); } void IpeTransforming::MouseRelease(QMouseEvent *) { Compute(iCanvas->Pos()); QString s; switch (iType) { case EMove: s = QObject::tr("object move"); break; case ERotate: s = QObject::tr("rotation"); break; case EScale: s = QObject::tr("scaling"); break; case EStretch: s = QObject::tr("stretching"); break; } iServices->OvSvcAddUndoItem(new IpePage(*iPage), s); // Update the objects for (IpePage::iterator it = iPage->begin(); it != iPage->end(); ++it) { if (it->Select() != IpePgObject::ENone) it->Transform(iTransform); } iPage->SetEdited(true); iCanvas->FinishOverlay(); } void IpeTransforming::MouseMove(QMouseEvent *) { Compute(iCanvas->Pos()); iCanvas->UpdateOverlay(); } // compute iTransform void IpeTransforming::Compute(const IpeVector &v1) { IpeVector u0 = iMouseDown - iOrigin; IpeVector u1 = v1 - iOrigin; switch (iType) { case EMove: if (iWithShift) { IpeVector d = v1 - iMouseDown; if (IpeAbs(d.iX) > IpeAbs(d.iY)) iTransform = IpeMatrix(IpeVector(d.iX, 0)); else iTransform = IpeMatrix(IpeVector(0, d.iY)); } else iTransform = IpeMatrix(v1 - iMouseDown); break; case ERotate: iTransform = (IpeMatrix(iOrigin) * IpeLinear(IpeAngle(u1.Angle() - u0.Angle())) * IpeMatrix(-iOrigin)); break; case EScale: { double factor = sqrt(u1.SqLen() / u0.SqLen()); iTransform = (IpeMatrix(iOrigin) * IpeLinear(factor, 0, 0, factor) * IpeMatrix(-iOrigin)); } break; case EStretch: { double xfactor = u0.iX == 0.0 ? 1.0 : u1.iX / u0.iX; double yfactor = u0.iY == 0.0 ? 1.0 : u1.iY / u0.iY; iTransform = (IpeMatrix(iOrigin) * IpeLinear(xfactor, 0, 0, yfactor) * IpeMatrix(-iOrigin)); } break; } } // --------------------------------------------------------------------