File indexing completed on 2024-05-19 04:36:38

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 "NodeTool.h"
0021 #include "ResizeHandle.h"
0022 #include "RotateHandle.h"
0023 #include "MoveHandle.h"
0024 #include "NodeItem.h"
0025 #include "DocumentPrivate.h"
0026 #include "ViewPrivate.h"
0027 #include "Renderer.h"
0028 
0029 #include <tikz/core/Style.h>
0030 #include <tikz/core/Transaction.h>
0031 #include <tikz/core/UndoSetProperty.h>
0032 
0033 #include <QApplication>
0034 #include <QGraphicsScene>
0035 #include <QKeyEvent>
0036 #include <QInputDialog>
0037 
0038 #include <QDebug>
0039 
0040 #include <cmath>
0041 
0042 namespace tikz {
0043 namespace ui {
0044 
0045 static void setProp(const tikz::core::Uid & entity, const QString & key, const QVariant & value)
0046 {
0047     entity.document()->addUndoItem(new tikz::core::UndoSetProperty(entity, key, value));
0048 }
0049 
0050 NodeTool::NodeTool(NodeItem * node, QGraphicsScene * scene)
0051     : AbstractTool(node->document(), scene)
0052     , m_node(node)
0053     , m_transaction(nullptr)
0054 {
0055     // show all node handles
0056     createNodeHandles();
0057 
0058     connect(m_node, SIGNAL(changed()), this, SLOT(updateHandlePositions()));
0059 }
0060 
0061 NodeTool::~NodeTool()
0062 {
0063     qDeleteAll(m_handles);
0064     m_handles.clear();
0065 }
0066 
0067 void NodeTool::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
0068 {
0069 }
0070 
0071 void NodeTool::mousePressEvent(QGraphicsSceneMouseEvent * event)
0072 {
0073 }
0074 
0075 void NodeTool::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
0076 {
0077 }
0078 
0079 void NodeTool::keyPressEvent(QKeyEvent * event)
0080 {
0081     if (event->key() == Qt::Key_Escape) {
0082         if (m_transaction && m_transaction->isRunning()) {
0083             m_transaction->cancel();
0084             event->accept();
0085         }
0086     }
0087 
0088     if (event->key() == Qt::Key_Delete) {
0089         document()->deleteNodeItem(m_node);
0090     }
0091 
0092     if (event->key() == Qt::Key_T) {
0093         bool ok = true;
0094         QString text = QInputDialog::getMultiLineText(nullptr, "Set Text", "Enter node text in LaTeX", m_node->node()->text(), &ok);
0095         if (ok) {
0096             setProp(m_node->uid(), "text", text);
0097         }
0098     }
0099 }
0100 
0101 void NodeTool::createNodeHandles()
0102 {
0103     // on hide, just delte all handles
0104     qDeleteAll(m_handles);
0105     m_handles.clear();
0106 
0107     // create and show handles
0108     m_handles.append(new ResizeHandle(Handle::TopLeftCorner));
0109     m_handles.append(new ResizeHandle(Handle::TopRightCorner));
0110     m_handles.append(new ResizeHandle(Handle::BottomLeftCorner));
0111     m_handles.append(new ResizeHandle(Handle::BottomRightCorner));
0112     m_handles.append(new ResizeHandle(Handle::LeftBorder));
0113     m_handles.append(new ResizeHandle(Handle::TopBorder));
0114     m_handles.append(new ResizeHandle(Handle::RightBorder));
0115     m_handles.append(new ResizeHandle(Handle::BottomBorder));
0116     m_handles.append(new ResizeHandle(Handle::TopLeftCorner));
0117     m_handles.append(new MoveHandle(Handle::Center));
0118     m_handles.append(new RotateHandle(Handle::ResizePos));
0119 
0120     // make sure the handles are positioned correctly
0121     updateHandlePositions();
0122 
0123     // show and connect to get handle movements
0124     for (Handle * handle: qAsConst(m_handles)) {
0125         scene()->addItem(handle);
0126         handle->show();
0127         connect(handle, SIGNAL(positionChanged(tikz::ui::Handle *, const QPointF &, QGraphicsView *)),
0128                 this, SLOT(handleMoved(tikz::ui::Handle *, const QPointF &, QGraphicsView *)));
0129         connect(handle, SIGNAL(mousePressed(tikz::ui::Handle *, const QPointF &, QGraphicsView *)),
0130                 this, SLOT(handleMousePressed(tikz::ui::Handle *, const QPointF &, QGraphicsView *)));
0131         connect(handle, SIGNAL(mouseReleased(tikz::ui::Handle *, const QPointF &, QGraphicsView *)),
0132                 this, SLOT(handleMouseReleased(tikz::ui::Handle *, const QPointF &, QGraphicsView *)));
0133     }
0134 }
0135 
0136 void NodeTool::updateHandlePositions()
0137 {
0138     for (Handle * handle : qAsConst(m_handles)) {
0139         handle->setPos(handlePos(handle->handlePos()));
0140         handle->setRotation(-m_node->style()->rotation());
0141     }
0142 }
0143 
0144 QPointF NodeTool::handlePos(Handle::Position pos)
0145 {
0146     const QPointF c = m_node->node()->pos();
0147     const QRectF r = m_node->shapeRect();
0148     const qreal w = r.width() / 2.0;
0149     const qreal h = r.height() / 2.0;
0150     QPointF p(0, 0);
0151 
0152     switch (pos) {
0153         case Handle::TopLeftCorner: p = QPointF(-w, h); break;
0154         case Handle::TopBorder: p = QPointF(0, h); break;
0155         case Handle::TopRightCorner: p = QPointF(w, h); break;
0156         case Handle::LeftBorder: p = QPointF(-w, 0); break;
0157         case Handle::Center: break;
0158         case Handle::RightBorder: p = QPointF(w, 0); break;
0159         case Handle::BottomLeftCorner: p = QPointF(-w, -h); break;
0160         case Handle::BottomBorder: p = QPointF(0, -h); break;
0161         case Handle::BottomRightCorner: p = QPointF(w, -h); break;
0162         case Handle::ResizePos: p = QPointF(0, -h - tikz::mm2pt(5)); break;
0163         default: Q_ASSERT(false);
0164     }
0165     QTransform t;
0166     t.rotate(m_node->style()->rotation());
0167     return c + t.map(p);
0168 }
0169 
0170 void NodeTool::handleMoved(Handle * handle, const QPointF & scenePos, QGraphicsView * view)
0171 {
0172     if (!m_transaction || ! m_transaction->isRunning()) {
0173         return;
0174     }
0175 
0176     auto tikzView = qobject_cast<Renderer *>(view);
0177 
0178     // later: preferred unit
0179     const tikz::Unit unit = tikz::Unit::Centimeter;
0180 
0181     //
0182     // rotate
0183     //
0184     if (handle->handleType() == Handle::RotateHandle) {
0185         const QPointF delta = m_node->node()->pos() - tikz::Pos(scenePos);
0186         const qreal rad = atan2(-delta.y(), -delta.x());
0187         const qreal deg = tikzView->snapAngle(rad * 180 / M_PI + 90);
0188         setProp(m_node->style()->uid(), "rotation", deg);
0189         return;
0190     }
0191 
0192     //
0193     // move
0194     //
0195     if (handle->handlePos() == Handle::Center) {
0196         tikz::Pos p = tikz::Pos(scenePos).convertTo(unit);
0197         p = tikzView->snapPos(p);
0198         m_node->node()->setPos(p);
0199         return;
0200     }
0201 
0202     //
0203     // resize
0204     //
0205     QTransform t;
0206     t.rotate(-m_node->style()->rotation());
0207 
0208     // honor rotation of node
0209     const tikz::Pos delta = tikz::Pos(2 * t.map(m_node->node()->pos() - tikz::Pos(scenePos))).convertTo(unit);
0210     const tikz::Value oldW = m_node->style()->minimumWidth();
0211     const tikz::Value oldH = m_node->style()->minimumHeight();
0212     tikz::Value w = m_node->style()->minimumWidth();
0213     tikz::Value h = m_node->style()->minimumHeight();
0214 
0215     switch (handle->handlePos()) {
0216         case Handle::TopLeftCorner:
0217         case Handle::TopRightCorner:
0218         case Handle::BottomLeftCorner:
0219         case Handle::BottomRightCorner: {
0220             w = delta.x();
0221             h = delta.y();
0222 
0223             // snap to raster
0224             w = tikzView->snapValue(w);
0225             h = tikzView->snapValue(h);
0226 
0227             break;
0228         }
0229         case Handle::TopBorder:
0230         case Handle::BottomBorder: {
0231             h = delta.y();
0232             // snap to raster
0233             h = tikzView->snapValue(h);
0234             break;
0235         }
0236         case Handle::LeftBorder:
0237         case Handle::RightBorder: {
0238             w = delta.x();
0239             // snap to raster
0240             w = tikzView->snapValue(w);
0241             break;
0242         }
0243         case Handle::Center: Q_ASSERT(false);
0244         case Handle::ResizePos: Q_ASSERT(false);
0245         default: Q_ASSERT(false);
0246     }
0247 
0248     // for now, only allow positive values
0249     w = tikz::Value(qAbs(w.value()), w.unit());
0250     h = tikz::Value(qAbs(h.value()), h.unit());
0251 
0252     if (w != oldW) setProp(m_node->style()->uid(), "minimumWidth", w);
0253     if (h != oldH) setProp(m_node->style()->uid(), "minimumHeight", h);
0254 }
0255 
0256 void NodeTool::handleMousePressed(Handle * handle, const QPointF & scenePos, QGraphicsView * view)
0257 {
0258     qDebug() << "mouse handle pressed " << scenePos;
0259 
0260     QString action;
0261     switch (handle->handleType()) {
0262         case Handle::MoveHandle: action = "Move Node"; break;
0263         case Handle::ResizeHandle: action = "Resize Node"; break;
0264         case Handle::RotateHandle: action = "Rotate Node"; break;
0265         default: Q_ASSERT(false);
0266     }
0267 
0268     m_transaction.reset(new tikz::core::Transaction(document(), action));
0269 }
0270 
0271 void NodeTool::handleMouseReleased(Handle * handle, const QPointF & scenePos, QGraphicsView * view)
0272 {
0273     qDebug() << "mouse handle released" << scenePos;
0274 
0275     m_transaction.reset();
0276 }
0277 
0278 }
0279 }
0280 
0281 // kate: indent-width 4; replace-tabs on;