File indexing completed on 2024-04-21 04:34:00

0001 /********************************************************************************
0002  * Copyright (C) 2011-2015 by Stephen Allewell                                  *
0003  * steve.allewell@gmail.com                                                     *
0004  *                                                                              *
0005  * This program is free software; you can redistribute it and/or modify         *
0006  * it under the terms of the GNU General Public License as published by         *
0007  * the Free Software Foundation; either version 2 of the License, or            *
0008  * (at your option) any later version.                                          *
0009  ********************************************************************************/
0010 
0011 
0012 /**
0013  * @file
0014  * Implement the Editor class.
0015  */
0016 
0017 
0018 /**
0019  * @page editor_window Editor
0020  * The editor allows the user to create or modify the current symbol. It consists of a fixed size square grid
0021  * with several tools to create QPainterPath objects which will then be used to render the symbol. The QPainterPath
0022  * consists of one or more subpaths. Each subpath has a start point, which defaults to (0,0), but can be moved as
0023  * required. The various tools require one or more additional points to define the subpath. For each of the
0024  * tools used,the last point of the last tool becomes the first point of the next. Consequently all the individual
0025  * tools are joined together to create a single subpath. A new subpath can be started by using the move to option
0026  * to specify a new start point. Additionally the use of the rectangle and ellipse tools will create stand alone subpaths.
0027  *
0028  * The editor shows a green square which is the largest preferred size of the symbol. This allows some white space
0029  * around the symbol when it will be rendered in a pattern.
0030  *
0031  * When placing points, guide lines will be shown when the cursor is at a position that aligns with other
0032  * points either horizontally, vertically or at a range of predefined angles. The circle guides are created if the cursor
0033  * is at a position that aligns with another point on a circle where the center is at the center of the grid.
0034  * When a symbol shape is made up of a large number of points, the number of guide lines generated can be significant.
0035  * If these become a problem then the generation of the guide lines can be turned off completely or an option is
0036  * available in the configuration dialog to simplify the range of angles tested.
0037  *
0038  * @section editor_tools Editor Tools
0039  *
0040  * @subsection move_to Move To
0041  * Allows moving the start point of the current subpath. If the current subpath already has elements in
0042  * it, this effectively closes the current subpath and creates a new one.
0043  *
0044  * @subsection line_to Line To
0045  * Draws a line from the last point to the next selected point.
0046  *
0047  * @subsection cubic_to Cubic To
0048  * Draws a cubic spline consisting of a start point which will be the last point of the last command, two
0049  * control points and a finish point. The control points define the path of the curve between the start and end points.
0050  *
0051  * @subsection rectangle Rectangle
0052  * Draw a rectangle between two points. The points define the opposite corners of the rectangle. These can either
0053  * entered by clicking two points or clicking and dragging. When dragging, a rubber band rectangle will be shown
0054  * indicating the rectangle.
0055  *
0056  * @subsection ellipse Ellipse
0057  * Draw an ellipse between two points. The points define the opposite corners of a rectangle encompassing the
0058  * ellipse. As with the rectangle the points can be selected by clicking two points or clicking and dragging.
0059  * When dragging, a rubber band ellipse will be shown indicating the ellipse.
0060  *
0061  * @subsection character Insert Character
0062  * This will display a character selection dialog allowing selection of a character from any font to be inserted as a
0063  * symbol. Double click the required character which will then be converted to a symbol format in the editor. Further changes
0064  * can then be made to it as needed. This selection will replace any current symbol definition in the editor. The dialog
0065  * will remain open, allowing a select, save, repeat work flow rapidly building up a library of symbols.
0066  *
0067  * @subsection rotate_left Rotate Left
0068  * Rotate the symbol counter clockwise 90 degrees.
0069  *
0070  * @subsection rotate_right Rotate Right
0071  * Rotate the symbol clockwise 90 degrees.
0072  *
0073  * @subsection flip_horizontal Flip Horizontally
0074  * Flip the symbol horizontally about an axis passing vertically through the center of the grid.
0075  *
0076  * @subsection flip_vertical Flip Vertically
0077  * Flip the symbol vertically about an axis passing horizontally through the center of the grid.
0078  *
0079  * @subsection scale_preferred Scale to Preferred Size
0080  * Scale the existing symbol so that it fits within the preferred size square.
0081  *
0082  * @subsection snap_grid Snap to Grid
0083  * The selection of points can either be snapped to the grid or can be freely placed depending on if
0084  * the snap option is toggled on or off.
0085  *
0086  * @subsection guide_lines Enable Guide Lines
0087  * The generation of guide lines can be toggled on or off. As the number of points in a symbol increases
0088  * the number of guide lines that can be created can become significant at which point they become less
0089  * useful.
0090  *
0091  * @section path_rendering Path Rendering
0092  *
0093  * @subsection fill_mode Fill Mode
0094  * The defined path can either be set to filled or outline depending on if the fill mode is toggled
0095  * on or off.
0096  *
0097  * @subsection fill_rule Fill Rule
0098  * When filling is enabled the path will be filled. The rule defining the fill will be either Winding  of
0099  * Odd-Even.
0100  *
0101  * @par Winding
0102  * The winding rule will completely fill the objects boundary.
0103  *
0104  * @par Odd Even
0105  * The odd even rule will fill produce a checker board style fill where adjacent areas will be different
0106  * to each other.
0107  *
0108  * @subsection line_cap Line End Cap Style
0109  * The start and end of a path line can have different visual appearance depending on the following
0110  * attributes.
0111  *
0112  * @par Flat Cap
0113  * The line is stopped at the end point and the line end is cut square to the direction.
0114  *
0115  * @par Square Cap
0116  * The line is projected beyond the end point by half the line width and the line end is cut square to
0117  * the direction.
0118  *
0119  * @par Round Cap
0120  * The line is projected beyond the end point by half the line width and the line end is rounded.
0121  *
0122  * @subsection line_join Line Join Style
0123  * Where two lines join, the way the join is shown is determined by the join style as follows.
0124  *
0125  * @par Bevel Join
0126  * The lines are beveled at the intersection.
0127  *
0128  * @par Miter Join
0129  * The lines are extended and mitered to provide a corner.
0130  *
0131  * @par Round Join
0132  * The lines are joined with a rounded corner.
0133  *
0134  * @subsection line_width Line Width
0135  * The line width can be increased and decreased. The width is rendered equally about the line between
0136  * the two points. This attribute is mostly relevant to unfilled shapes.
0137  *
0138  * @section points Points
0139  * The points can either be snapped to the grid intersections or placed freely depending on the snap option.
0140  * All points can be moved by hovering over them until the cursor changes to a move cursor, then hold down
0141  * the left mouse button and drag the point to its new location.
0142  *
0143  * @section editing_symbols Editing Symbols
0144  * Editing a symbol in the library can be done by clicking the symbol in the library view. This will
0145  * transfer the symbol into the editor allowing it to be changed and saved. In this scenario there is a link
0146  * between the symbol being edited and the library, so saving does update the correct symbol. If you wish to
0147  * use this symbol as a basis for other symbols using rotation and flipping, use the File->Save Symbol as New
0148  * command which will save the symbol to the library as a new one. Further changes to the symbol which are
0149  * saved will also become new symbols.
0150  */
0151 
0152 
0153 #include "Editor.h"
0154 
0155 #include <QAction>
0156 #include <QMouseEvent>
0157 #include <QPainter>
0158 #include <QString>
0159 
0160 #include <KCharSelect>
0161 #include <KLocalizedString>
0162 
0163 #include <math.h>
0164 
0165 #include "SymbolEditor.h"
0166 
0167 
0168 const int pointsRequired[] = {1, 1, 3, 2, 2};   /**< The number of points required for commands from Editor::ToolMode */
0169 
0170 const char *statusMessages[][3] = {
0171     {
0172         I18N_NOOP("Select a new starting position."),
0173         "",
0174         ""
0175     },
0176     {
0177         I18N_NOOP("Select the line end point."),
0178         "",
0179         ""
0180     },
0181     {
0182         I18N_NOOP("Select the first control point."),
0183         I18N_NOOP("Select the second control point."),
0184         I18N_NOOP("Select the end point"),
0185     },
0186     {
0187         I18N_NOOP("Select the first corner."),
0188         I18N_NOOP("Select the second corner."),
0189         ""
0190     },
0191     {
0192         I18N_NOOP("Select the first corner of the bounding rectangle."),
0193         I18N_NOOP("Select the second corner of the bounding rectangle."),
0194         ""
0195     },
0196     {
0197         I18N_NOOP("Double click a character to insert into the editor."),
0198         "",
0199         ""
0200     }
0201 };
0202 
0203 
0204 /**
0205  * Construct the Editor.
0206  * The editor is a sub class of a QWidget and is added to a layout widget.
0207  * The boundary edges of the symbol editor are defined in the range 0..1. These are used to intersect
0208  * the guidelines which are shown at the m_angles relative to existing points
0209  *
0210  * @param parent a pointer to the parent widget, this is passed to the base class object
0211  */
0212 Editor::Editor(QWidget *parent)
0213     :   QWidget(parent),
0214         m_index(0),
0215         m_charSelect(nullptr)
0216 {
0217     readSettings();
0218 
0219     setMouseTracking(true);
0220     setFocusPolicy(Qt::StrongFocus);
0221 
0222     m_topEdge = QLineF(0.0, 0.0, 1.0, 0.0);
0223     m_bottomEdge = QLineF(0.0, 1.0, 1.0, 1.0);
0224     m_leftEdge = QLineF(0.0, 0.0, 0.0, 1.0);
0225     m_rightEdge = QLineF(1.0, 0, 1.0, 1.0);
0226 }
0227 
0228 
0229 /**
0230  * Destructor for the Editor.
0231  */
0232 Editor::~Editor()
0233 {
0234     delete m_charSelect;
0235 }
0236 
0237 
0238 /**
0239  * Get the index and Symbol.
0240  * Update the Symbol with the path being edited.
0241  *
0242  * @return the edited Symbol and it's index in a QPair
0243  */
0244 QPair<qint16, Symbol> Editor::symbol()
0245 {
0246     m_symbol.setPath(m_painterPath);
0247     return QPair<qint16, Symbol>(m_index, m_symbol);
0248 }
0249 
0250 
0251 /**
0252  * Set the symbol and index to be edited.
0253  * The undo stack will be cleared.
0254  *
0255  * @param pair a QPair consisting of the index and the Symbol
0256  */
0257 void Editor::setSymbol(const QPair<qint16, Symbol> &pair)
0258 {
0259     m_undoStack.clear();
0260     m_index = pair.first;
0261     m_symbol = pair.second;
0262     m_painterPath = m_symbol.path();
0263     deconstructPainterPath();
0264     update();
0265 }
0266 
0267 
0268 /**
0269  * Move the start of the sub path.
0270  * The point is in symbol coordinates.
0271  * Returns the current painterPath for later undo.
0272  *
0273  * @param to a const reference to a QPointF representing the new start position
0274  *
0275  * @return a QPainterPath representing the original path
0276  */
0277 QPainterPath Editor::moveTo(const QPointF &to)
0278 {
0279     QPainterPath path = m_painterPath;
0280     m_painterPath.moveTo(to);
0281     deconstructPainterPath();
0282     update();
0283     return path;
0284 }
0285 
0286 
0287 /**
0288  * Add a line to the sub path.
0289  * The point is in symbol coordinates.
0290  * Returns the current painterPath for later undo.
0291  *
0292  * @param to a const reference to a QPointF representing the end point of the line
0293  *
0294  * @return a QPainterPath representing the original path
0295  */
0296 QPainterPath Editor::lineTo(const QPointF &to)
0297 {
0298     QPainterPath path = m_painterPath;
0299     m_painterPath.lineTo(to);
0300     deconstructPainterPath();
0301     update();
0302     return path;
0303 }
0304 
0305 
0306 /**
0307  * Add a cubic spline to the sub path.
0308  * The points are in symbol coordinates.
0309  * Returns the current painterPath for later undo.
0310  *
0311  * @param control1 a const reference to a QPointF representing the first control point
0312  * @param control2 a const reference to a QPointF representing the second control point
0313  * @param to a const reference to a QPointF representing the end point of the curve
0314  *
0315  * @return a QPainterPath representing the original path
0316  */
0317 QPainterPath Editor::cubicTo(const QPointF &control1, const QPointF &control2, const QPointF &to)
0318 {
0319     QPainterPath path = m_painterPath;
0320     m_painterPath.cubicTo(control1, control2, to);
0321     deconstructPainterPath();
0322     update();
0323     return path;
0324 }
0325 
0326 
0327 /**
0328  * Add a rectangle as a new sub path.
0329  * The points are in symbol coordinates.
0330  * Returns the current painterPath for later undo.
0331  *
0332  * @param from a const reference to a QPointF representing the first corner
0333  * @param to a const reference to a QPointF representing the second corner
0334  *
0335  * @return a QPainterPath representing the original path
0336  */
0337 QPainterPath Editor::addRectangle(const QPointF &from, const QPointF &to)
0338 {
0339     QPainterPath path = m_painterPath;
0340     m_painterPath.addRect(QRectF(from, to));
0341     deconstructPainterPath();
0342     update();
0343     return path;
0344 }
0345 
0346 
0347 /**
0348  * Add an ellipse as a new sub path.
0349  * The points are in symbol coordinates.
0350  * Returns the current painterPath for later undo.
0351  *
0352  * @param from a const reference to a QPointF representing the first corner
0353  * @param to a const reference to a QPointF representing the second corner
0354  *
0355  * @return a QPainterPath representing the original path
0356  */
0357 QPainterPath Editor::addEllipse(const QPointF &from, const QPointF &to)
0358 {
0359     QPainterPath path = m_painterPath;
0360     m_painterPath.addEllipse(QRectF(from, to));
0361     deconstructPainterPath();
0362     update();
0363     return path;
0364 }
0365 
0366 
0367 /**
0368  * Remove the last added element by restoring the path that existed before the last command.
0369  *
0370  * @param path a const reference to a QPainterPath being restored
0371  */
0372 void Editor::removeLast(const QPainterPath& path)
0373 {
0374     m_painterPath = path;
0375     deconstructPainterPath();
0376     update();
0377 }
0378 
0379 
0380 /**
0381  * Move a point node to a new position.
0382  *
0383  * @param index the index of the point to move
0384  * @param to a const reference to a QPointF representing the new position
0385  */
0386 void Editor::movePoint(int index, const QPointF &to)
0387 {
0388     m_points[index] = to;
0389     constructPainterPath();
0390     update();
0391 }
0392 
0393 
0394 /**
0395  * Rotate all the points around the center of the symbol 90 degrees counter clockwise.
0396  */
0397 void Editor::rotatePointsLeft()
0398 {
0399     for (int i = 0 ; i < m_points.count() ; ++i) {
0400         QPointF p = m_points[i];
0401         m_points[i] = QPointF(p.y(), 1.0 - p.x());
0402     }
0403 
0404     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
0405         QPointF p = m_activePoints[i];
0406         m_activePoints[i] = QPointF(p.y(), 1.0 - p.x());
0407     }
0408 
0409     constructPainterPath();
0410     update();
0411 }
0412 
0413 
0414 /**
0415  * Rotate all the points around the center of the symbol 90 degrees clockwise
0416  */
0417 void Editor::rotatePointsRight()
0418 {
0419     for (int i = 0 ; i < m_points.count() ; ++i) {
0420         QPointF p = m_points[i];
0421         m_points[i] = QPointF(1.0 - p.y(), p.x());
0422     }
0423 
0424     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
0425         QPointF p = m_activePoints[i];
0426         m_activePoints[i] = QPointF(1.0 - p.y(), p.x());
0427     }
0428 
0429     constructPainterPath();
0430     update();
0431 }
0432 
0433 
0434 /**
0435  * Flip all the points about a vertical axis passing through the symbol center.
0436  */
0437 void Editor::flipPointsHorizontal()
0438 {
0439     for (int i = 0 ; i < m_points.count() ; ++i) {
0440         QPointF p = m_points[i];
0441         p.setX(1.0 - p.x());
0442         m_points[i] = p;
0443     }
0444 
0445     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
0446         QPointF p = m_activePoints[i];
0447         p.setX(1.0 - p.x());
0448         m_activePoints[i] = p;
0449     }
0450 
0451     constructPainterPath();
0452     update();
0453 }
0454 
0455 
0456 /**
0457  * Flip all the points about a horizontal axis passing through the symbol center.
0458  */
0459 void Editor::flipPointsVertical()
0460 {
0461     for (int i = 0 ; i < m_points.count() ; ++i) {
0462         QPointF p = m_points[i];
0463         p.setY(1.0 - p.y());
0464         m_points[i] = p;
0465     }
0466 
0467     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
0468         QPointF p = m_activePoints[i];
0469         p.setY(1.0 - p.y());
0470         m_activePoints[i] = p;
0471     }
0472 
0473     constructPainterPath();
0474     update();
0475 }
0476 
0477 
0478 /**
0479  * Set the fill state of the symbols path.
0480  *
0481  * @param filled @c true if the path is to be filled, @c false for outline paths
0482  */
0483 void Editor::setFilled(bool filled)
0484 {
0485     m_symbol.setFilled(filled);
0486     update();
0487 }
0488 
0489 
0490 /**
0491  * Set the fill rule of the symbols path.
0492  *
0493  * @param rule a Qt::FillRule value, see the QPainterPath documentation for details
0494  */
0495 void Editor::setFillRule(Qt::FillRule rule)
0496 {
0497     m_painterPath.setFillRule(rule);
0498     update();
0499 }
0500 
0501 
0502 /**
0503  * Set the pen cap style, see the QPen documentation for details on the styles.
0504  *
0505  * @param capStyle a Qt::PenCapStyle value
0506  */
0507 void Editor::setCapStyle(Qt::PenCapStyle capStyle)
0508 {
0509     m_symbol.setCapStyle(capStyle);
0510     update();
0511 }
0512 
0513 
0514 /**
0515  * Set the pen join style, see the QPen documentation for details on the styles.
0516  *
0517  * @param joinStyle a Qt::PenJoinStyle value
0518  */
0519 void Editor::setJoinStyle(Qt::PenJoinStyle joinStyle)
0520 {
0521     m_symbol.setJoinStyle(joinStyle);
0522     update();
0523 }
0524 
0525 
0526 /**
0527  * Set the line width.
0528  * The width parameter is rounded to 2 decimal places to avoid errors in the comparisons.
0529  * Signals are emitted to enable or disable the relevant actions depending on the values.
0530  *
0531  * @param width the new line width
0532  */
0533 void Editor::setLineWidth(double width)
0534 {
0535     width = round(width * 100) / 100;
0536     m_symbol.setLineWidth(width);
0537     emit minLineWidth(width == 0.01);
0538     emit maxLineWidth(width == 1.00);
0539     update();
0540 }
0541 
0542 
0543 /**
0544  * Set the painter path to the requested path updating the points lists and the view.
0545  *
0546  * @param path contains the new path
0547  *
0548  * @return the old path
0549  */
0550 QPainterPath Editor::setPath(const QPainterPath &path)
0551 {
0552     QPainterPath originalPath = m_painterPath;
0553     m_painterPath = path;
0554     deconstructPainterPath();
0555     update();
0556 
0557     return originalPath;
0558 }
0559 
0560 
0561 /**
0562  * Clear the editor to represent a blank symbol.
0563  * Delete the existing data and update the display.
0564  */
0565 void Editor::clear()
0566 {
0567     m_undoStack.clear();
0568     m_points.clear();
0569     m_activePoints.clear();
0570     m_elements.clear();
0571     m_index = 0;
0572     m_symbol = Symbol();
0573     m_painterPath = m_symbol.path();
0574     update();
0575 }
0576 
0577 
0578 /**
0579  * Get a pointer to the undo stack.
0580  *
0581  * @return a pointer to the QUndoStack
0582  */
0583 QUndoStack *Editor::undoStack()
0584 {
0585     return &m_undoStack;
0586 }
0587 
0588 
0589 /**
0590  * Select the next tool.
0591  * Remove any points that may have been used in the last tool command but not yet committed.
0592  * Set the tool mode from the sending actions data.
0593  * If the character selector tool is selected, display a KCharSelect widget to get a character to be
0594  * inserted otherwise hide the KCharSelect widget.
0595  *
0596  * @param action a pointer to the action triggering the selection
0597  */
0598 void Editor::selectTool(QAction *action)
0599 {
0600     m_activePoints.clear();
0601     m_toolMode = static_cast<Editor::ToolMode>(action->data().toInt());
0602 
0603     if (m_toolMode == Editor::Character) {
0604         if (m_charSelect == nullptr) {
0605             m_charSelect = new KCharSelect(0, 0);
0606             connect(m_charSelect, SIGNAL(charSelected(QChar)), this, SLOT(charSelected(QChar)));
0607         }
0608 
0609         m_charSelect->show();
0610     } else if (m_charSelect) {
0611         m_charSelect->hide();
0612     }
0613 
0614     updateStatusMessage();
0615 }
0616 
0617 
0618 /**
0619  * Called when a character is selected from the KCharSelect widget. Create a QPainterPath object and
0620  * make it available to the editor for insertion.
0621  *
0622  * @param char a QChar containing the character selected.
0623  */
0624 void Editor::charSelected(const QChar &character)
0625 {
0626     QPainterPath path;
0627     QFont font = m_charSelect->currentFont();
0628     path.addText(0.0, 0.0, font, QString(character));
0629 
0630     // scale the path to fit the bounding rectangle to fit in a rectangle 0,0-1,1  maintaining
0631     // aspect ratio.
0632     QRectF boundingRect = path.boundingRect();
0633 
0634     double scale = double(m_gridElements - m_borderSize - m_borderSize) / double(std::max(boundingRect.width(), boundingRect.height()) * m_gridElements);
0635     QTransform transform = QTransform::fromTranslate(-boundingRect.center().x(), -boundingRect.center().y()) * QTransform::fromScale(scale, scale) * QTransform::fromTranslate(0.5, 0.5);
0636     path = transform.map(path);
0637 
0638     m_undoStack.push(new AddCharacterCommand(this, path));
0639 }
0640 
0641 
0642 /**
0643  * Switch the snap mode on or off.
0644  *
0645  * @param enabled true if on, false otherwise
0646  */
0647 void Editor::enableSnap(bool enabled)
0648 {
0649     m_snap = enabled;
0650 }
0651 
0652 
0653 /**
0654  * Switch the generation of guides on or off.
0655  *
0656  * @param enabled true if on, false otherwise
0657  */
0658 void Editor::enableGuides(bool enabled)
0659 {
0660     m_guides = enabled;
0661 }
0662 
0663 
0664 /**
0665  * Switch the fill mode on or off.
0666  *
0667  * @param filled true if on, false otherwise
0668  */
0669 void Editor::selectFilled(bool filled)
0670 {
0671     m_undoStack.push(new ChangeFilledCommand(this, m_symbol.filled(), filled));
0672 }
0673 
0674 
0675 /**
0676  * Select the fill rule to be used when filling is enabled.
0677  * The specified rule is taken from the sending actions data.
0678  *
0679  * @param action a pointer to the action triggering the selection
0680  */
0681 void Editor::selectFillRule(QAction *action)
0682 {
0683     Qt::FillRule fillRule = static_cast<Qt::FillRule>(action->data().toInt());
0684     m_undoStack.push(new ChangeFillRuleCommand(this, m_symbol.path().fillRule(), fillRule));
0685 }
0686 
0687 
0688 /**
0689  * Select the pen cap style.
0690  * The specified value is taken from the sending actions data.
0691  *
0692  * @param action a pointer to the action that triggered the selection
0693  */
0694 void Editor::selectCapStyle(QAction *action)
0695 {
0696     Qt::PenCapStyle capStyle = static_cast<Qt::PenCapStyle>(action->data().toInt());
0697     m_undoStack.push(new ChangeCapStyleCommand(this, m_symbol.capStyle(), capStyle));
0698 }
0699 
0700 
0701 /**
0702  * Select the pen join style.
0703  * The specified value is taken from the sending actions data.
0704  *
0705  * @param action a pointer to the action that triggered the selection
0706  */
0707 void Editor::selectJoinStyle(QAction *action)
0708 {
0709     Qt::PenJoinStyle joinStyle = static_cast<Qt::PenJoinStyle>(action->data().toInt());
0710     m_undoStack.push(new ChangeJoinStyleCommand(this, m_symbol.joinStyle(), joinStyle));
0711 }
0712 
0713 
0714 /**
0715  * Increase the thickness of the path.
0716  */
0717 void Editor::increaseLineWidth()
0718 {
0719     m_undoStack.push(new IncreaseLineWidthCommand(this, m_symbol.lineWidth(), m_symbol.lineWidth() + 0.01));
0720 }
0721 
0722 
0723 /**
0724  * Decrease the thickness of the path.
0725  */
0726 void Editor::decreaseLineWidth()
0727 {
0728     m_undoStack.push(new DecreaseLineWidthCommand(this, m_symbol.lineWidth(), m_symbol.lineWidth() - 0.01));
0729 }
0730 
0731 
0732 /**
0733  * Rotate the symbol left (counter clock wise)
0734  */
0735 void Editor::rotateLeft()
0736 {
0737     m_undoStack.push(new RotateLeftCommand(this));
0738 }
0739 
0740 
0741 /**
0742  * Rotate the symbol right (clock wise)
0743  */
0744 void Editor::rotateRight()
0745 {
0746     m_undoStack.push(new RotateRightCommand(this));
0747 }
0748 
0749 
0750 /**
0751  * Flip the symbol horizontally about the vertical center line.
0752  */
0753 void Editor::flipHorizontal()
0754 {
0755     m_undoStack.push(new FlipHorizontalCommand(this));
0756 }
0757 
0758 
0759 /**
0760  * Flip the symbol vertically about the horizontal center line.
0761  */
0762 void Editor::flipVertical()
0763 {
0764     m_undoStack.push(new FlipVerticalCommand(this));
0765 }
0766 
0767 
0768 /**
0769  * Scale the symbol to fit within the preferred size.
0770  */
0771 void Editor::scalePreferred()
0772 {
0773     m_undoStack.push(new ScalePreferredCommand(this, m_painterPath, m_gridElements, m_borderSize));
0774 }
0775 
0776 
0777 /**
0778  * Read the settings from the configuration file and apply them.
0779  * The widget is resized to accommodate a gridElements number of cells of width elementSize. An
0780  * additional 1 is added to allow for a right and bottom edge to be drawn.
0781  */
0782 void Editor::readSettings()
0783 {
0784     m_gridElements       = Configuration::editor_GridElements();
0785     m_elementSize        = Configuration::editor_ElementSize();
0786     m_elementGrouping    = Configuration::editor_ElementGrouping();
0787     m_pointSize          = static_cast<double>(Configuration::editor_PointSize()) / 500;
0788     m_snapThreshold      = 1.0 / m_gridElements * Configuration::editor_SnapThreshold();
0789     m_borderSize         = Configuration::editor_BorderSize();
0790     m_preferredSizeColor = Configuration::editor_PreferredSizeColor();
0791     m_guideLineColor     = Configuration::editor_GuideLineColor();
0792 
0793     m_angles.clear();
0794 
0795     if (Configuration::editor_SimplifiedGuideLines()) {
0796         m_angles << 0 << 45 << 90 << 135;
0797     } else {
0798         m_angles << 0 << 15 << 30 << 45 << 60 << 75 << 90 << 105 << 120 << 135 << 150 << 165;
0799     }
0800 
0801     int minimumSize = m_elementSize * m_gridElements + 1;
0802     setMinimumSize(minimumSize, minimumSize);
0803 }
0804 
0805 
0806 /**
0807  * Process the mouse press event. Effective for the left mouse button pressed.
0808  * Test if the cursor is over an existing point in which case dragging of the
0809  * point is enabled. Otherwise a point is added to the active points.
0810  *
0811  * @param event a pointer to a QMouseEvent
0812  */
0813 void Editor::mousePressEvent(QMouseEvent *event)
0814 {
0815     QPoint p = event->pos();
0816     m_dragging = false;
0817 
0818     if (event->buttons() & Qt::LeftButton) {
0819         m_start = m_tracking = snapPoint(p);
0820         m_rubberBand = QRectF();
0821 
0822         if (node(toSymbol(p))) {
0823             m_dragging = true;
0824             m_dragPointIndex = nodeUnderCursor(toSymbol(p));
0825         } else {
0826             addPoint(m_start);
0827         }
0828     }
0829 
0830     update();
0831 }
0832 
0833 
0834 /**
0835  * Process the mouse move event. Effective for the left mouse button pressed.
0836  * Clear any currently defined guides.
0837  * If dragging a point, update the points position and update the view, otherwise
0838  * if the toolmode is for a rectangle or an ellipse initialize the rubber band
0839  * rectangle and update the view.
0840  * If the left button is not pressed, test if the cursor is over an existing point
0841  * and if it is change the cursor to a SizeAllCursor to indicate it can be moved,
0842  * otherwise reset it to an ArrowCursor.
0843  * Guide lines are constructed for each of the existing points to show if the new
0844  * position lies in line with or at a specific angle to or on a circular path to
0845  * that point.
0846  *
0847  * @param event a pointer to a QMouseEvent
0848  */
0849 void Editor::mouseMoveEvent(QMouseEvent *event)
0850 {
0851     QPoint p = event->pos();
0852 
0853     if (event->buttons() & Qt::LeftButton) {
0854         if (m_tracking != snapPoint(p)) {
0855             m_tracking = snapPoint(p);
0856 
0857             if (m_dragging) {
0858                 if (m_dragPointIndex.first) {
0859                     m_points[m_dragPointIndex.second] = m_tracking;
0860                     constructPainterPath();
0861                 } else {
0862                     m_activePoints[m_dragPointIndex.second] = m_tracking;
0863                 }
0864             } else if (m_toolMode == Rectangle || m_toolMode == Ellipse) {
0865                 m_rubberBand = QRectF(m_start, m_tracking).normalized();
0866             }
0867 
0868             update();
0869         }
0870     } else {
0871         if (node(toSymbol(p))) {
0872             setCursor(Qt::SizeAllCursor);
0873         } else {
0874             setCursor(Qt::ArrowCursor);
0875         }
0876     }
0877 
0878     constructGuides(toSymbol(p));
0879 
0880     update();
0881 }
0882 
0883 
0884 /**
0885  * Process the mouse release event.
0886  * If dragging a point that is in the m_points list and it hasn't been dragged back to
0887  * its start position create a MovePointCommand and push it onto the editor undo stack.
0888  * Pushing commands onto the stack will execute it, although the point is already in its
0889  * new position, this will have no adverse effects, but will record the move for possible
0890  * undo/redo later.
0891  * If not dragging a point, clear the rubber band rectangle and add a point if the new
0892  * point is not the same as the starting point.
0893  *
0894  * @param event a pointer to a QMouseEvent
0895  */
0896 void Editor::mouseReleaseEvent(QMouseEvent *event)
0897 {
0898     QPointF p = snapPoint(event->pos());
0899 
0900     if (m_dragging) {
0901         if ((p != m_start) && (m_dragPointIndex.first)) {
0902             m_undoStack.push(new MovePointCommand(this, m_dragPointIndex.second, m_start, p));
0903         }
0904 
0905         m_dragging = false;
0906     } else if (m_toolMode == Rectangle || m_toolMode == Ellipse) {
0907         m_rubberBand = QRectF();
0908 
0909         if (p != m_start) {
0910             addPoint(p);
0911         }
0912     }
0913 
0914     update();
0915 }
0916 
0917 
0918 /**
0919  * Add a point for the current tool.
0920  * Each tool requires a number of points to be defined to fully describe the shape the
0921  * tool represents. This function adds the points and keeps track of the number of points
0922  * required.  When the number of points required have been selected, the tool command can
0923  * be implemented and added to the symbol undo stack.
0924  * The points are in symbol coordinates in the range 0..1
0925  *
0926  * @param point a const reference to a QPointF
0927  */
0928 void Editor::addPoint(const QPointF &point)
0929 {
0930     m_activePoints.append(point);
0931 
0932     if (m_activePoints.count() == pointsRequired[m_toolMode]) {
0933         switch (m_toolMode) {
0934         case MoveTo:
0935             m_undoStack.push(new MoveToCommand(this, m_activePoints.at(0)));
0936             break;
0937 
0938         case LineTo:
0939             m_undoStack.push(new LineToCommand(this, m_activePoints.at(0)));
0940             break;
0941 
0942         case CubicTo:
0943             m_undoStack.push(new CubicToCommand(this, m_activePoints.at(0), m_activePoints.at(1), m_activePoints.at(2)));
0944             break;
0945 
0946         case Rectangle:
0947             m_undoStack.push(new RectangleCommand(this, m_activePoints.at(0), m_activePoints.at(1)));
0948             break;
0949 
0950         case Ellipse:
0951             m_undoStack.push(new EllipseCommand(this, m_activePoints.at(0), m_activePoints.at(1)));
0952             break;
0953 
0954         case Character: // Insertion of characters does not require points
0955             break;
0956         }
0957 
0958         m_activePoints.clear();
0959     }
0960 
0961     update();
0962     updateStatusMessage();
0963 }
0964 
0965 
0966 /**
0967  * Update the status message dependent on the command in use and the number of active points
0968  * in the list.
0969  */
0970 void Editor::updateStatusMessage()
0971 {
0972     emit message(i18n(statusMessages[m_toolMode][m_activePoints.count()]));
0973 }
0974 
0975 
0976 /**
0977  * Read settings and set the element size based on the size of the editor window
0978  *
0979  * @param event a pointer to the QResizeEvent
0980  */
0981 void Editor::resizeEvent(QResizeEvent *event)
0982 {
0983     QSize requestedSize = event->size();
0984 
0985     int requestedWidth = requestedSize.width();
0986     int requestedHeight = requestedSize.height();
0987     int side = std::min(requestedWidth, requestedHeight);
0988 
0989     m_elementSize = side / m_gridElements;
0990     m_size = m_elementSize * m_gridElements;
0991 
0992     resize(m_size, m_size);
0993     move((requestedWidth - m_size) / 2, (requestedHeight - m_size) / 2);
0994 }
0995 
0996 
0997 /**
0998  * Paint the contents of the editor.
0999  * This will fill the background and draw a grid based on the elementSize and elementGroup.
1000  * For each element of the current path the control points are drawn with suitable lines
1001  * joining them, for example for a cubic curve, a curve is drawn, but the control points
1002  * are joined with dashed lines.
1003  * A complete path is constructed and painted in a light color with transparency to show
1004  * the current symbol shape.
1005  *
1006  * @param event a pointer to a QPaintEvent
1007  */
1008 void Editor::paintEvent(QPaintEvent *event)
1009 {
1010     // initialise the painter
1011     QPainter p(this);
1012     p.setRenderHint(QPainter::Antialiasing, true);
1013     p.fillRect(event->rect(), Qt::white);
1014 
1015     QPen lightGray(Qt::lightGray);
1016     lightGray.setCosmetic(true);
1017 
1018     QPen darkGray(Qt::darkGray);
1019     darkGray.setCosmetic(true);
1020 
1021     // scale the painter to suit the number of elements in the grid
1022     p.setWindow(0, 0, m_gridElements, m_gridElements);
1023 
1024     // draw vertical grid
1025     for (int x = 0 ; x <= m_gridElements ; ++x) {
1026         if (x % m_elementGrouping) {
1027             p.setPen(lightGray);
1028         } else {
1029             p.setPen(darkGray);
1030         }
1031 
1032         p.drawLine(x, 0, x, m_gridElements);
1033     }
1034 
1035     // draw horizontal grid
1036     for (int y = 0 ; y <= m_gridElements ; ++y) {
1037         if (y % m_elementGrouping) {
1038             p.setPen(lightGray);
1039         } else {
1040             p.setPen(darkGray);
1041         }
1042 
1043         p.drawLine(0, y, m_gridElements, y);
1044     }
1045 
1046     // draw a rectangle representing the preferred symbol size allowing for some white space
1047     QRectF preferredSizeRect = QRectF(0, 0, m_gridElements, m_gridElements).adjusted(m_borderSize, m_borderSize, -m_borderSize, -m_borderSize);
1048 
1049     QColor preferredSizeColor(m_preferredSizeColor);
1050     preferredSizeColor.setAlpha(128);
1051 
1052     QPen preferredSizeColorPen(preferredSizeColor);
1053     preferredSizeColorPen.setCosmetic(true);
1054 
1055     p.setPen(preferredSizeColorPen);
1056     p.setBrush(Qt::NoBrush);
1057     p.drawRect(preferredSizeRect);
1058 
1059     // define a rectangle for the points
1060     QRectF dot(0, 0, m_pointSize, m_pointSize);
1061 
1062     // create a dashed pen for use with curve references and a wide pen for the shape
1063     QPen dashedPen(Qt::DashLine);
1064     dashedPen.setCosmetic(true);
1065 
1066     // draw all the points as a circle
1067     p.setBrush(Qt::SolidPattern);
1068 
1069     // scale the painter to draw the points and symbols
1070     p.setWindow(0, 0, 1, 1);
1071 
1072     for (int i = 0 ; i < m_points.count() ; ++i) {
1073         QPointF s = m_points.at(i);
1074         dot.moveCenter(s);
1075         p.drawEllipse(dot);
1076     }
1077 
1078     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
1079         QPointF s = m_activePoints.at(i);
1080         dot.moveCenter(s);
1081         p.drawEllipse(dot);
1082     }
1083 
1084     // iterate through the elements and for each curve element draw the reference lines with a dashed pen
1085     for (int i = 0, j = 0 ; i < m_elements.count() ; ++i) {
1086         QPainterPath::ElementType element = m_elements[i];
1087         QPointF s;
1088         QPointF e;
1089         QPointF c1;
1090         QPointF c2;
1091 
1092         switch (element) {
1093         case QPainterPath::MoveToElement:
1094             // increment to next pointer
1095             ++j;
1096             break;
1097 
1098         case QPainterPath::LineToElement:
1099             // increment to next pointer
1100             j++;
1101             break;
1102 
1103         case QPainterPath::CurveToElement:
1104             p.setPen(dashedPen);
1105             s = m_points.at(j - 1);
1106             c1 = m_points.at(j++);
1107             c2 = m_points.at(j++);
1108             e = m_points.at(j++);
1109             p.drawLine(s, c1);
1110             p.drawLine(c1, c2);
1111             p.drawLine(c2, e);
1112             break;
1113 
1114         case QPainterPath::CurveToDataElement: // this is only used within the constructed QPainterPath
1115             break;
1116         }
1117     }
1118 
1119 
1120     // draw the rubber band rectangle or the active points for the current command
1121     if (m_rubberBand.isValid()) {
1122         QPen blackPen(Qt::black);
1123         blackPen.setCosmetic(true);
1124         p.setPen(blackPen);
1125         p.setBrush(Qt::NoBrush);
1126 
1127         if (m_toolMode == Rectangle) {
1128             p.drawRect(m_rubberBand);
1129         } else {
1130             p.drawEllipse(m_rubberBand);
1131         }
1132     } else if (m_activePoints.count() && m_toolMode != Rectangle && m_toolMode != Ellipse) {
1133         p.setPen(dashedPen);
1134         QPointF s;
1135 
1136         if (m_points.count()) {
1137             s = m_points.last();
1138         }
1139 
1140         for (int i = 0 ; i < m_activePoints.count() ; ++i) {
1141             QPointF e = m_activePoints[i];
1142             p.drawLine(s, e);
1143             s = e;
1144         }
1145     }
1146 
1147     // scale the painter and draw the path
1148     QColor c(Qt::black);
1149     c.setAlpha(128);
1150     QPen pathPen(m_symbol.pen());
1151     pathPen.setColor(c);
1152     p.setPen(pathPen);
1153 
1154     QBrush pathFill(m_symbol.brush());
1155     pathFill.setColor(c);
1156     p.setBrush(pathFill);
1157 
1158     p.drawPath(m_painterPath);
1159 
1160     // draw the guidelines
1161     QColor guideLineColor(m_guideLineColor);
1162     guideLineColor.setAlpha(128);
1163 
1164     QPen guideLinePen(guideLineColor);
1165     guideLinePen.setCosmetic(true);
1166 
1167     p.setPen(guideLinePen);
1168     p.setBrush(Qt::NoBrush);
1169     QRectF snapRect(0, 0, 0.03, 0.03);
1170 
1171     foreach (const QPointF & snapPoint, m_snapPoints) {
1172         snapRect.moveCenter(snapPoint);
1173         p.drawRect(snapRect);
1174     }
1175 
1176     foreach (const QLineF & guideLine, m_guideLines) {
1177         p.drawLine(guideLine);
1178     }
1179 
1180     foreach (qreal guideCircle, m_guideCircles) {
1181         p.drawEllipse(QPointF(0.5, 0.5), guideCircle, guideCircle);
1182     }
1183 }
1184 
1185 
1186 /**
1187  * Process key presses to check for Escape to clear the current points being entered.
1188  *
1189  * @param event a pointer to a QKeyEvent
1190  */
1191 void Editor::keyPressEvent(QKeyEvent *event)
1192 {
1193     switch (event->key()) {
1194     case Qt::Key_Escape:
1195         m_activePoints.clear();
1196         update();
1197         break;
1198 
1199     default:
1200         QWidget::keyPressEvent(event);
1201         break;
1202     }
1203 }
1204 
1205 
1206 /**
1207  * Convert a point to a symbol point.
1208  *
1209  * The point will have come from the mouse events pos() value and will be converted to
1210  * a value representing a point in the symbol, not necessarily at a snap position.
1211  *
1212  * @param point a const reference to a QPoint
1213  *
1214  * @return a QPointF representing the point in symbol coordinates between 0..1
1215  */
1216 QPointF Editor::toSymbol(const QPoint &point) const
1217 {
1218     double sx = static_cast<double>(point.x()) / m_size;
1219     double sy = static_cast<double>(point.y()) / m_size;
1220     return QPointF(sx, sy);
1221 }
1222 
1223 
1224 /**
1225  * Convert a point to a snap point.
1226  *
1227  * The point comes from the mouse events pos() value and will be converted to a value
1228  * in symbol coordinates. If snapping is enabled, the point will be snapped to a guide
1229  * or to a grid intersection. If snapping is disabled, the point is returned converted
1230  * to the symbol coordinates.
1231  *
1232  * @param point a const reference to a QPoint
1233  *
1234  * @return a QPointF representing the symbol coordinates either snapped or not.
1235  */
1236 QPointF Editor::snapPoint(const QPoint &point) const
1237 {
1238     QPair<bool, QPointF> snap = snapToGuide(toSymbol(point));
1239 
1240     if (!snap.first) {
1241         snap = snapToGrid(point);
1242     }
1243 
1244     return snap.second;
1245 }
1246 
1247 
1248 /**
1249  * Convert a point to a symbol snap point.
1250  * The point will have come from the mouse events pos() value and will be converted to
1251  * a value that represents an intersection of the grid lines. This will happen only if
1252  * snapping is enabled.
1253  *
1254  * @param point a const reference to a QPoint
1255  *
1256  * @return a QPair<bool, QPointF> the bool element determines if a snap point was found represented by the QPointF element
1257  */
1258 QPair<bool, QPointF> Editor::snapToGrid(const QPoint &point) const
1259 {
1260     QPair<bool, QPointF> snap(false, toSymbol(point));
1261 
1262     if (m_snap) {
1263         double sx = round(static_cast<double>(point.x()) * m_gridElements / (m_size)) / m_gridElements;
1264         double sy = round(static_cast<double>(point.y()) * m_gridElements / (m_size)) / m_gridElements;
1265         snap.first = true;
1266         snap.second = QPointF(sx, sy);
1267     }
1268 
1269     return snap;
1270 }
1271 
1272 
1273 /**
1274  * Convert a point to a guide snap point.
1275  * The point will have come from the mouse events pos() value and will be converted to
1276  * a value that is within the threshold of a calculated guide intersection. This will
1277  * happen only if the snapping is enabled.
1278  *
1279  * @param point a const reference to a QPointF
1280  *
1281  * @return a QPair<bool, QPointF> the bool element determines if a snap point was found represented by the QPointF element
1282  */
1283 QPair<bool, QPointF> Editor::snapToGuide(const QPointF &point) const
1284 {
1285     QPair<bool, QPointF> snap(false, point);
1286 
1287     if (m_snap) {
1288         foreach (const QPointF & p, m_snapPoints) {
1289             if ((point - p).manhattanLength() < m_snapThreshold) {
1290                 snap.first = true;
1291                 snap.second = p;
1292                 break;
1293             }
1294         }
1295     }
1296 
1297     return snap;
1298 }
1299 
1300 
1301 /**
1302  * Test if a control point is at the point specified.
1303  * This will test both the committed m_points and the uncommitted m_activePoints
1304  *
1305  * @param point a const reference to a QPointF representing the symbol coordinate
1306  *
1307  * @return true if a node exists here, false otherwise
1308  */
1309 bool Editor::node(const QPointF &point) const
1310 {
1311     bool found = false;
1312 
1313     for (int i = 0 ; i < m_points.count() ; ++i) {
1314         QPointF distance = point - m_points[i];
1315 
1316         if (distance.manhattanLength() < m_snapThreshold) {
1317             found = true;
1318         }
1319     }
1320 
1321     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
1322         QPointF distance = point - m_activePoints[i];
1323 
1324         if (distance.manhattanLength() < m_snapThreshold) {
1325             found = true;
1326         }
1327     }
1328 
1329     return found;
1330 }
1331 
1332 
1333 /**
1334  * Find the index of the node that is at the point specified.
1335  * This will test both the committed m_points and the uncommitted m_activePoints
1336  *
1337  * @param point a const reference to a QPointF representing the point to find
1338  *
1339  * @return a QPair<bool, int> representing the list and the index of the node, true if committed, false otherwise
1340  * -1 is returned as the index if the node was not found
1341  */
1342 QPair<bool, int> Editor::nodeUnderCursor(const QPointF &point) const
1343 {
1344     for (int i = 0 ; i < m_points.count() ; ++i) {
1345         QPointF distance = point - m_points[i];
1346 
1347         if (distance.manhattanLength() < m_snapThreshold) {
1348             return QPair<bool, int>(true, i);
1349         }
1350     }
1351 
1352     for (int i = 0 ; i < m_activePoints.count() ; ++i) {
1353         QPointF distance = point - m_activePoints[i];
1354 
1355         if (distance.manhattanLength() < m_snapThreshold) {
1356             return QPair<bool, int>(false, i);
1357         }
1358     }
1359 
1360     return QPair<bool, int>(false, -1);
1361 }
1362 
1363 
1364 /**
1365  * Convert a QPainterPath to commands and points.
1366  * Clear the current lists of points.
1367  * Iterate all the elements of the QPainterPath adding commands and points as necessary.
1368  */
1369 void Editor::deconstructPainterPath()
1370 {
1371     m_points.clear();
1372     m_activePoints.clear();
1373     m_elements.clear();
1374 
1375     for (int i = 0 ; i < m_painterPath.elementCount() ; ++i) {
1376         QPainterPath::Element e = m_painterPath.elementAt(i);
1377 
1378         switch (e.type) {
1379         case QPainterPath::MoveToElement:
1380             m_elements.append(e.type);
1381             m_points.append(QPointF(e));
1382             break;
1383 
1384         case QPainterPath::LineToElement:
1385             m_elements.append(e.type);
1386             m_points.append(QPointF(e));
1387             break;
1388 
1389         case QPainterPath::CurveToElement:
1390             m_elements.append(e.type);
1391             m_points.append(QPointF(e));
1392             break;
1393 
1394         case QPainterPath::CurveToDataElement:
1395             m_points.append(QPointF(e));
1396             break;
1397         }
1398     }
1399 }
1400 
1401 
1402 /**
1403  * Construct a QPainterPath from the commands and points.
1404  * Initialize an empty QPainterPath.
1405  * Iterate all elements stored in m_elements and add them to the path.
1406  */
1407 void Editor::constructPainterPath()
1408 {
1409     QPainterPath path;;
1410 
1411     for (int i = 0, j = 0 ; i < m_elements.count() ; ++i) {
1412         QPainterPath::ElementType e = m_elements[i];
1413 
1414         switch (e) {
1415         case QPainterPath::MoveToElement:
1416             path.moveTo(m_points[j++]);
1417             break;
1418 
1419         case QPainterPath::LineToElement:
1420             path.lineTo(m_points[j++]);
1421             break;
1422 
1423         case QPainterPath::CurveToElement:
1424             path.cubicTo(m_points[j], m_points[j + 1], m_points[j + 2]);
1425             j += 3;
1426             break;
1427 
1428         case QPainterPath::CurveToDataElement: // this only appears within the QPainterPath
1429             break;
1430         }
1431     }
1432 
1433     path.setFillRule(m_painterPath.fillRule());
1434     m_painterPath = path;
1435 }
1436 
1437 
1438 /**
1439  * Construct guides for the cursor position point being tested relative to the other points.
1440  * Iterate through points in m_points and m_active points projecting them out in the directions
1441  * being tested to determine if they intersect any existing guide lines at a point near to the
1442  * cursor position. If found they are added to the list of relevant lines and the snap point of the
1443  * intersection is added to snap points.
1444  * A list of circles is created where the radius to the point is similar to the test point. Intersection
1445  * points are calculated for the projected lines and if these are close to the cursor position they
1446  * are added to the snap points along with the circles and lines.
1447  * If the generation of guide lines is turned off, the existing guides are cleared on no more are created.
1448  *
1449  * @param to a const reference to a QPointF representing the cursor position
1450  */
1451 void Editor::constructGuides(const QPointF &to)
1452 {
1453     m_guideLines.clear();
1454     m_guideCircles.clear();
1455     m_snapPoints.clear();
1456 
1457     if (!m_guides) {
1458         return;
1459     }
1460 
1461     QList<QPointF> points;
1462     points << m_points << m_activePoints;   // construct list of all points on screen
1463 
1464     QList<double> guideCircles;             // local list of circles, used for filtering
1465     QList<QLineF> guideLines;               // local list of lines, used for filtering
1466 
1467     double radiusTo = sqrt(pow(0.5 - to.x(), 2) + pow(0.5 - to.y(), 2));
1468 
1469     foreach (const QPointF &from, points) {
1470         if ((from - to).manhattanLength() < m_snapThreshold * 2) {
1471             continue;    // cursor close to existing point so ignore it
1472         }
1473 
1474         double radiusFrom = sqrt(pow(0.5 - from.x(), 2) + pow(0.5 - from.y(), 2));
1475 
1476         if (fabs(radiusTo - radiusFrom) < m_snapThreshold) {
1477             guideCircles.append(radiusFrom);
1478         }
1479 
1480         // construct line guides
1481         QLineF guideLine(from, from + QPointF(1.0, 0.0));
1482 
1483         foreach (double angle, m_angles) {
1484             guideLine.setAngle(angle);
1485             QLineF projectedGuideLine = projected(guideLine);
1486 
1487             QPointF intersection;
1488 
1489             foreach (const QLineF &line, guideLines) {
1490                 if (projectedGuideLine.intersect(line, &intersection)) {
1491                     if (((to - intersection).manhattanLength() < m_snapThreshold)) {
1492                         addGuideLine(line);
1493                         addGuideLine(projectedGuideLine);
1494                         addSnapPoint(intersection);
1495                     }
1496                 }
1497             }
1498 
1499             guideLines.append(projectedGuideLine);
1500         }
1501 
1502         // construct circle guides
1503         foreach (double radius, guideCircles) {
1504             foreach (const QLineF &line, guideLines) {
1505                 double ax = line.x1();
1506                 double ay = line.y1();
1507                 double bx = line.x2();
1508                 double by = line.y2();
1509                 double cx = 0.5;
1510                 double cy = 0.5;
1511 
1512                 double lab = sqrt(pow(bx - ax, 2.0) + pow(by - ay, 2.0));
1513                 double dx = (bx - ax) / lab;
1514                 double dy = (by - ay) / lab;
1515 
1516                 double t = dx * (cx - ax) + dy * (cy - ay);
1517 
1518                 double ex = t * dx + ax;
1519                 double ey = t * dy + ay;
1520 
1521                 double lec = sqrt(pow(ex - cx, 2.0) + pow(ey - cy, 2.0));
1522 
1523                 if (lec < radius) {
1524                     double dt = sqrt(pow(radius, 2.0) - pow(lec, 2.0));
1525 
1526                     // first intersection
1527                     double fx = (t - dt) * dx + ax;
1528                     double fy = (t - dt) * dy + ay;
1529 
1530                     if ((QPointF(fx, fy) - to).manhattanLength() < m_snapThreshold) {
1531                         addGuideCircle(radius);
1532                         addGuideLine(line);
1533                         addSnapPoint(QPointF(fx, fy));
1534                     }
1535 
1536                     // second intersection
1537                     double gx = (t + dt) * dx + ax;
1538                     double gy = (t + dt) * dy + ay;
1539 
1540                     if ((QPointF(gx, gy) - to).manhattanLength() < m_snapThreshold) {
1541                         addGuideCircle(radius);
1542                         addGuideLine(line);
1543                         addSnapPoint(QPointF(gx, gy));
1544                     }
1545                 } else if (lec == radius) {
1546                     // intersection is ex, ey
1547                     if ((QPointF(ex, ey) - to).manhattanLength() < m_snapThreshold) {
1548                         addGuideCircle(radius);
1549                         addGuideLine(line);
1550                         addSnapPoint(QPointF(ex, ey));
1551                     }
1552                 }
1553             }
1554         }
1555     }
1556 }
1557 
1558 
1559 /**
1560  * Add a guide line to m_guideLines after checking it doesn't exist yet
1561  *
1562  * @param line a const reference to a QLineF representing the reference line
1563  */
1564 void Editor::addGuideLine(const QLineF &line)
1565 {
1566     if (!m_guideLines.contains(line)) {
1567         m_guideLines.append(line);
1568     }
1569 }
1570 
1571 
1572 /**
1573  * Add a guide circle to m_guideCircles after checking it doesn't exist yet
1574  *
1575  * @param radius a double representing the circle at center (0.5, 0.5)
1576  */
1577 void Editor::addGuideCircle(double radius)
1578 {
1579     if (!m_guideCircles.contains(radius)) {
1580         m_guideCircles.append(radius);
1581     }
1582 }
1583 
1584 
1585 /**
1586  * Add a snap point to m_snapPoints after checking it doesn't exist yet
1587  *
1588  * @param point a const reference to a QPointF representing the snap position
1589  */
1590 void Editor::addSnapPoint(const QPointF &point)
1591 {
1592     if (!m_snapPoints.contains(point)) {
1593         m_snapPoints.append(point);
1594     }
1595 }
1596 
1597 
1598 /**
1599  * Project the line to the edges of the grid.
1600  * Calculate the points where the projected line would intersect the edges.
1601  * Check which edges get intersected within the coordinate 0..1 which will determine
1602  * which points are required to construct the projected line.
1603  *
1604  * @param line a const reference to a QLineF
1605  *
1606  * @return a QLineF representing the projected line
1607  */
1608 QLineF Editor::projected(const QLineF &line) const
1609 {
1610     QPointF intersectTop;
1611     QPointF intersectBottom;
1612     QPointF intersectLeft;
1613     QPointF intersectRight;
1614 
1615     QLineF::IntersectType t = line.intersect(m_topEdge, &intersectTop);;
1616     line.intersect(m_bottomEdge, &intersectBottom);
1617     line.intersect(m_leftEdge, &intersectLeft);
1618     line.intersect(m_rightEdge, &intersectRight);
1619 
1620     if (t == QLineF::NoIntersection) {      // horizontal line
1621         return QLineF(intersectLeft, intersectRight);
1622     }
1623 
1624     return QLineF(intersectTop, intersectBottom);
1625 }
1626 
1627 #include "moc_Editor.cpp"