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"