File indexing completed on 2024-04-21 15:23:39

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