File indexing completed on 2024-05-26 04:38:41

0001 /* This file is part of the TikZKit project.
0002  *
0003  * Copyright (C) 2014 Dominik Haumann <dhaumann@kde.org>
0004  *
0005  * This library is free software; you can redistribute it and/or modify
0006  * it under the terms of the GNU Library General Public License as published
0007  * by the Free Software Foundation, either version 2 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, see
0017  * <http://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include "EllipseTool.h"
0021 #include "ResizeHandle.h"
0022 #include "RotateHandle.h"
0023 #include "MoveHandle.h"
0024 #include "EllipsePathItem.h"
0025 #include "AnchorManager.h"
0026 #include "DocumentPrivate.h"
0027 #include "ViewPrivate.h"
0028 #include "Renderer.h"
0029 
0030 #include <tikz/core/Style.h>
0031 #include <tikz/core/EllipsePath.h>
0032 #include <tikz/core/Transaction.h>
0033 #include <tikz/core/UndoSetProperty.h>
0034 
0035 #include <QApplication>
0036 #include <QGraphicsScene>
0037 
0038 #include <QDebug>
0039 
0040 #include <cmath>
0041 
0042 namespace tikz {
0043 namespace ui {
0044 
0045 EllipseTool::EllipseTool(tikz::ui::PathItem * path, QGraphicsScene * scene)
0046     : AbstractTool(path->document(), scene)
0047     , m_path(qobject_cast<tikz::ui::EllipsePathItem *>(path))
0048     , m_anchorManager(new AnchorManager(scene, path->document(), this))
0049     , m_transaction(nullptr)
0050 {
0051     // show all path handles
0052     createPathHandles();
0053 
0054     connect(m_path->path(), SIGNAL(changed()), this, SLOT(updateHandlePositions()));
0055 }
0056 
0057 EllipseTool::~EllipseTool()
0058 {
0059     qDeleteAll(m_handles);
0060     m_handles.clear();
0061 }
0062 
0063 void EllipseTool::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
0064 {
0065 }
0066 
0067 void EllipseTool::mousePressEvent(QGraphicsSceneMouseEvent * event)
0068 {
0069 }
0070 
0071 void EllipseTool::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
0072 {
0073 }
0074 
0075 void EllipseTool::createPathHandles()
0076 {
0077     // on hide, just delte all handles
0078     qDeleteAll(m_handles);
0079     m_handles.clear();
0080 
0081     // create and show handles
0082     m_handles.append(new ResizeHandle(Handle::TopLeftCorner));
0083     m_handles.append(new ResizeHandle(Handle::TopRightCorner));
0084     m_handles.append(new ResizeHandle(Handle::BottomLeftCorner));
0085     m_handles.append(new ResizeHandle(Handle::BottomRightCorner));
0086     m_handles.append(new ResizeHandle(Handle::LeftBorder));
0087     m_handles.append(new ResizeHandle(Handle::TopBorder));
0088     m_handles.append(new ResizeHandle(Handle::RightBorder));
0089     m_handles.append(new ResizeHandle(Handle::BottomBorder));
0090     m_handles.append(new ResizeHandle(Handle::TopLeftCorner));
0091     m_handles.append(new MoveHandle(Handle::Center));
0092     m_handles.append(new RotateHandle(Handle::ResizePos));
0093 
0094     // make sure the handles are positioned correctly
0095     updateHandlePositions();
0096 
0097     // show and connect to get handle movements
0098     for (Handle * handle : qAsConst(m_handles)) {
0099         scene()->addItem(handle);
0100         handle->show();
0101         connect(handle, SIGNAL(positionChanged(tikz::ui::Handle *, const QPointF &, QGraphicsView *)),
0102                 this, SLOT(handleMoved(tikz::ui::Handle *, const QPointF &, QGraphicsView *)));
0103         connect(handle, SIGNAL(mousePressed(tikz::ui::Handle *, const QPointF &, QGraphicsView *)),
0104                 this, SLOT(handleMousePressed(tikz::ui::Handle *, const QPointF &, QGraphicsView *)));
0105         connect(handle, SIGNAL(mouseReleased(tikz::ui::Handle *, const QPointF &, QGraphicsView *)),
0106                 this, SLOT(handleMouseReleased(tikz::ui::Handle *, const QPointF &, QGraphicsView *)));
0107     }
0108 }
0109 
0110 void EllipseTool::updateHandlePositions()
0111 {
0112     for (Handle * handle : qAsConst(m_handles)) {
0113         handle->setPos(handlePos(handle->handlePos()));
0114         handle->setRotation(-m_path->style()->rotation());
0115     }
0116 }
0117 
0118 QPointF EllipseTool::handlePos(Handle::Position pos)
0119 {
0120     const QPointF c = m_path->pos();
0121     const qreal w = m_path->style()->radiusX().toPoint();
0122     const qreal h = m_path->style()->radiusY().toPoint();
0123     QPointF p(0, 0);
0124 
0125     switch (pos) {
0126         case Handle::TopLeftCorner: p = QPointF(-w, h); break;
0127         case Handle::TopBorder: p = QPointF(0, h); break;
0128         case Handle::TopRightCorner: p = QPointF(w, h); break;
0129         case Handle::LeftBorder: p = QPointF(-w, 0); break;
0130         case Handle::Center: break;
0131         case Handle::RightBorder: p = QPointF(w, 0); break;
0132         case Handle::BottomLeftCorner: p = QPointF(-w, -h); break;
0133         case Handle::BottomBorder: p = QPointF(0, -h); break;
0134         case Handle::BottomRightCorner: p = QPointF(w, -h); break;
0135         case Handle::ResizePos: p = QPointF(0, -h - tikz::mm2pt(5)); break;
0136         default: Q_ASSERT(false);
0137     }
0138     QTransform t;
0139     t.rotate(m_path->style()->rotation());
0140     return c + t.map(p);
0141 }
0142 
0143 void EllipseTool::handleMoved(Handle * handle, const QPointF & scenePos, QGraphicsView * view)
0144 {
0145     auto tikzView = qobject_cast<Renderer *>(view);
0146 
0147     //
0148     // rotate
0149     //
0150     if (handle->handleType() == Handle::RotateHandle) {
0151         const QPointF delta = m_path->pos() - scenePos;
0152         const qreal rad = atan2(-delta.y(), -delta.x());
0153         const qreal deg = tikzView->snapAngle(rad * 180 / M_PI + 90);
0154 
0155         m_path->document()->addUndoItem(new tikz::core::UndoSetProperty(m_path->path()->styleUid(), "rotation", deg));
0156 
0157         return;
0158     }
0159 
0160     // later: preferred unit
0161     const tikz::Unit unit = tikz::Unit::Centimeter;
0162 
0163     //
0164     // move
0165     //
0166     if (handle->handlePos() == Handle::Center) {
0167         // try to attach to anchor
0168         tikz::core::EllipsePath * ep = m_path->ellipsePath();
0169         tikz::core::MetaPos metaPos = m_anchorManager->anchorAt(scenePos, view);
0170         if (! metaPos.node()) {
0171             tikz::Pos p = tikz::Pos(scenePos).convertTo(unit);
0172             p = tikzView->snapPos(p);
0173             metaPos.setPos(p);
0174         }
0175 
0176         ep->setMetaPos(metaPos);
0177         return;
0178     }
0179 
0180     //
0181     // resize
0182     //
0183     QTransform t;
0184     t.rotate(-m_path->style()->rotation());
0185 
0186     // honor rotation of path
0187     const tikz::Pos delta = tikz::Pos(t.map(m_path->pos() - scenePos)).convertTo(unit);
0188     tikz::Value w = m_path->style()->radiusX();
0189     tikz::Value h = m_path->style()->radiusY();
0190 
0191     switch (handle->handlePos()) {
0192         case Handle::TopLeftCorner:
0193         case Handle::TopRightCorner:
0194         case Handle::BottomLeftCorner:
0195         case Handle::BottomRightCorner: {
0196             w = delta.x();
0197             h = delta.y();
0198 
0199             // snap to raster
0200             w = tikzView->snapValue(w);
0201             h = tikzView->snapValue(h);
0202 
0203             break;
0204         }
0205         case Handle::TopBorder:
0206         case Handle::BottomBorder: {
0207             h = delta.y();
0208             // snap to raster
0209             h = tikzView->snapValue(h);
0210             break;
0211         }
0212         case Handle::LeftBorder:
0213         case Handle::RightBorder: {
0214             w = delta.x();
0215             // snap to raster
0216             w = tikzView->snapValue(w);
0217             break;
0218         }
0219         case Handle::Center: Q_ASSERT(false);
0220         case Handle::ResizePos: Q_ASSERT(false);
0221         default: Q_ASSERT(false);
0222     }
0223 
0224     // for now, only allow positive values
0225     w = tikz::Value(qAbs(w.value()), w.unit());
0226     h = tikz::Value(qAbs(h.value()), h.unit());
0227 
0228     m_path->document()->addUndoItem(new tikz::core::UndoSetProperty(m_path->path()->styleUid(), "radiusX", w));
0229     m_path->document()->addUndoItem(new tikz::core::UndoSetProperty(m_path->path()->styleUid(), "radiusY", h));
0230 }
0231 
0232 void EllipseTool::handleMousePressed(Handle * handle, const QPointF & scenePos, QGraphicsView * view)
0233 {
0234 //     qDebug() << "ellipse tool: mouse handle pressed " << scenePos;
0235 
0236     QString action;
0237     switch (handle->handleType()) {
0238         case Handle::MoveHandle:
0239             m_anchorManager->addAllNodes();
0240             m_anchorManager->showAnchors();
0241             action = "Move Ellipse";
0242             break;
0243         case Handle::ResizeHandle:
0244             action = "Resize Ellipse";
0245             break;
0246         case Handle::RotateHandle:
0247             action = "Rotate Ellipse";
0248             break;
0249         default: Q_ASSERT(false);
0250     }
0251 
0252     m_transaction.reset(new tikz::core::Transaction(document(), action));
0253 }
0254 
0255 void EllipseTool::handleMouseReleased(Handle * handle, const QPointF & scenePos, QGraphicsView * view)
0256 {
0257 //     qDebug() << "ellipse tool:mouse handle released" << scenePos;
0258 
0259     m_transaction->finish();
0260     m_anchorManager->clear();
0261 }
0262 
0263 }
0264 }
0265 
0266 // kate: indent-width 4; replace-tabs on;