1 /****************************************************************************
2 **
3 ** This file is part of the LibreCAD project, a 2D CAD program
4 **
5 ** Copyright (C) 2018 A. Stebich (librecad@mail.lordofbikes.de)
6 ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
7 ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
8 **
9 **
10 ** This file may be distributed and/or modified under the terms of the
11 ** GNU General Public License version 2 as published by the Free Software
12 ** Foundation and appearing in the file gpl-2.0.txt included in the
13 ** packaging of this file.
14 **
15 ** This program is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ** GNU General Public License for more details.
19 **
20 ** You should have received a copy of the GNU General Public License
21 ** along with this program; if not, write to the Free Software
22 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 **
24 ** This copyright notice MUST APPEAR in all copies of the script!
25 **
26 **********************************************************************/
27 
28 #include <cmath>
29 #include <QAction>
30 #include <QMouseEvent>
31 #include "rs_actiondimangular.h"
32 #include "rs_dimangular.h"
33 
34 #include "rs_dialogfactory.h"
35 #include "rs_graphicview.h"
36 #include "rs_commandevent.h"
37 #include "rs_information.h"
38 #include "rs_coordinateevent.h"
39 #include "rs_preview.h"
40 #include "rs_debug.h"
41 #include "rs_math.h"
42 
RS_ActionDimAngular(RS_EntityContainer & container,RS_GraphicView & graphicView)43 RS_ActionDimAngular::RS_ActionDimAngular(RS_EntityContainer& container,
44                                          RS_GraphicView& graphicView) :
45     RS_ActionDimension( "Draw Angular Dimensions", container, graphicView)
46 {
47     reset();
48 }
49 
50 RS_ActionDimAngular::~RS_ActionDimAngular() = default;
51 
reset()52 void RS_ActionDimAngular::reset()
53 {
54     RS_ActionDimension::reset();
55 
56     actionType = RS2::ActionDimAngular;
57     edata.reset( new RS_DimAngularData( RS_Vector( false),
58                                         RS_Vector( false),
59                                         RS_Vector( false),
60                                         RS_Vector( false)) );
61     RS_DIALOGFACTORY->requestOptions( this, true, true);
62 }
63 
trigger()64 void RS_ActionDimAngular::trigger()
65 {
66     RS_PreviewActionInterface::trigger();
67 
68     if (line1.getStartpoint().valid && line2.getStartpoint().valid) {
69         RS_DimAngular* newEntity {new RS_DimAngular( container,
70                                                      *data,
71                                                      *edata)};
72 
73         newEntity->setLayerToActive();
74         newEntity->setPenToActive();
75         newEntity->update();
76         container->addEntity(newEntity);
77 
78         // upd. undo list:
79         if (document) {
80             document->startUndoCycle();
81             document->addUndoable(newEntity);
82             document->endUndoCycle();
83         }
84 
85         RS_Vector rz {graphicView->getRelativeZero()};
86         setStatus( SetLine1);
87         graphicView->redraw( RS2::RedrawDrawing);
88         graphicView->moveRelativeZero( rz);
89         RS_Snapper::finish();
90     }
91     else {
92         RS_DEBUG->print( "RS_ActionDimAngular::trigger: Entity is nullptr\n");
93     }
94 }
95 
mouseMoveEvent(QMouseEvent * e)96 void RS_ActionDimAngular::mouseMoveEvent(QMouseEvent* e)
97 {
98     RS_DEBUG->print( "RS_ActionDimAngular::mouseMoveEvent begin");
99 
100     switch (getStatus()) {
101     case SetPos:
102         if( setData( snapPoint(e))) {
103             RS_DimAngular *d {new RS_DimAngular( preview.get(), *data, *edata)};
104 
105             deletePreview();
106             preview->addEntity(d);
107             d->update();
108             drawPreview();
109         }
110         break;
111 
112     default:
113         break;
114     }
115 
116     RS_DEBUG->print("RS_ActionDimAngular::mouseMoveEvent end");
117 }
118 
mouseReleaseEvent(QMouseEvent * e)119 void RS_ActionDimAngular::mouseReleaseEvent(QMouseEvent* e)
120 {
121     if (Qt::LeftButton == e->button()) {
122         switch (getStatus()) {
123         case SetLine1: {
124             RS_Entity *en {catchEntity( e, RS2::ResolveAll)};
125             if (en && RS2::EntityLine == en->rtti()) {
126                 line1 = *dynamic_cast<RS_Line*>(en);
127                 click1 = line1.getNearestPointOnEntity( graphicView->toGraph( e->x(), e->y()));
128                 setStatus(SetLine2);
129             }
130             break; }
131 
132         case SetLine2: {
133             RS_Entity *en{catchEntity(e, RS2::ResolveAll)};
134             if (en && en->rtti()==RS2::EntityLine) {
135                 line2 = *dynamic_cast<RS_Line*>(en);
136                 click2 = line2.getNearestPointOnEntity( graphicView->toGraph( e->x(), e->y()));
137                 if( setData( click2, true)) {
138                     graphicView->moveRelativeZero( center);
139                     setStatus(SetPos);
140                 }
141             }
142             break; }
143 
144         case SetPos: {
145             RS_CoordinateEvent ce( snapPoint( e));
146             coordinateEvent( &ce);
147             break; }
148         }
149     }
150     else if (Qt::RightButton == e->button()) {
151         deletePreview();
152         init( getStatus() - 1);
153     }
154 }
155 
coordinateEvent(RS_CoordinateEvent * e)156 void RS_ActionDimAngular::coordinateEvent(RS_CoordinateEvent* e)
157 {
158     if ( ! e) {
159         return;
160     }
161 
162     switch (getStatus()) {
163     case SetPos:
164         if( setData( e->getCoordinate())) {
165             trigger();
166             reset();
167             setStatus( SetLine1);
168         }
169         break;
170 
171     default:
172         break;
173     }
174 }
175 
commandEvent(RS_CommandEvent * e)176 void RS_ActionDimAngular::commandEvent(RS_CommandEvent* e)
177 {
178     QString c( e->getCommand().toLower());
179 
180     if (checkCommand( QStringLiteral( "help"), c)) {
181         RS_DIALOGFACTORY->commandMessage( msgAvailableCommands()
182                                           + getAvailableCommands().join(", "));
183         return;
184     }
185 
186     // setting new text label:
187     if (SetText == getStatus()) {
188         setText( c);
189         RS_DIALOGFACTORY->requestOptions( this, true, true);
190         graphicView->enableCoordinateInput();
191         setStatus( lastStatus);
192         return;
193     }
194 
195     // command: text
196     if (checkCommand( QStringLiteral( "text"), c)) {
197         lastStatus = static_cast<Status>(getStatus());
198         graphicView->disableCoordinateInput();
199         setStatus( SetText);
200     }
201 }
202 
getAvailableCommands()203 QStringList RS_ActionDimAngular::getAvailableCommands()
204 {
205     QStringList cmd;
206 
207     switch (getStatus()) {
208     case SetLine1:
209     case SetLine2:
210     case SetPos:
211         cmd += command( QStringLiteral( "text"));
212         break;
213 
214     default:
215         break;
216     }
217 
218     return cmd;
219 }
220 
showOptions()221 void RS_ActionDimAngular::showOptions()
222 {
223     RS_ActionInterface::showOptions();
224 
225     RS_DIALOGFACTORY->requestOptions( this, true);
226 }
227 
hideOptions()228 void RS_ActionDimAngular::hideOptions()
229 {
230     RS_ActionInterface::hideOptions();
231 
232     RS_DIALOGFACTORY->requestOptions( this, false);
233 }
234 
updateMouseButtonHints()235 void RS_ActionDimAngular::updateMouseButtonHints()
236 {
237     switch (getStatus()) {
238     case SetLine1:
239         RS_DIALOGFACTORY->updateMouseWidget( tr("Select first line"),
240                                              tr("Cancel"));
241         break;
242 
243     case SetLine2:
244         RS_DIALOGFACTORY->updateMouseWidget( tr("Select second line"),
245                                              tr("Cancel"));
246         break;
247 
248     case SetPos:
249         RS_DIALOGFACTORY->updateMouseWidget( tr("Specify dimension arc line location"),
250                                              tr("Cancel"));
251         break;
252 
253     case SetText:
254         RS_DIALOGFACTORY->updateMouseWidget( tr("Enter dimension text:"), "");
255         break;
256 
257     default:
258         RS_DIALOGFACTORY->updateMouseWidget();
259         break;
260     }
261 }
262 
263 /**
264  * Justify one of the angle lines to ensure that the starting point
265  * of the line has the same angle from the intersection point as the
266  * selection click point and it is further away than the line end point
267  *
268  * @param line A selected line for the dimension
269  * @param click The click pos which selected the line
270  * @param center The intersection of the 2 lines to dimension
271  */
justify(RS_Line & line,const RS_Vector & click)272 void RS_ActionDimAngular::justify(RS_Line &line, const RS_Vector &click)
273 {
274     RS_Vector vStartPoint( line.getStartpoint());
275 
276     if( ! RS_Math::equal( vStartPoint.angleTo(center), click.angleTo( center))
277         || vStartPoint.distanceTo( center) < click.distanceTo( center)) {
278         line.reverse();
279     }
280 }
281 
282 /**
283  * Create a sorted array with angles from the lines intersection point
284  * to the starting points and their revers angles.
285  * Ensure, that line1 and line2 are in CCW order.
286  * Compute an offset for quadrant() method.
287  *
288  * @param line A selected line for the dimension
289  * @param click The click pos which selected the line
290  * @param center The intersection of the 2 lines to dimension
291  */
lineOrder(const RS_Vector & dimPos)292 void RS_ActionDimAngular::lineOrder(const RS_Vector &dimPos)
293 {
294     if( ! center.valid) {
295         return;
296     }
297 
298     // starting point angles and selection point angle from intersection point
299     double  a0  {(dimPos - center).angle()};
300     double  a1  {(line1.getStartpoint() - center).angle()};
301     double  a2  {(line2.getStartpoint() - center).angle()};
302 
303     // swap lines if necessary to ensure CCW order
304     if( RS_Math::correctAngle2( a1 - a0) > RS_Math::correctAngle2( a2 - a0)) {
305         RS_Line swapLines( line1);
306         line1 = line2;
307         line2 = swapLines;
308         double  swapAngle {a1};
309         a1 = a2;
310         a2 = swapAngle;
311     }
312 
313     // sorted array with starting point and reverse angles
314     angles.clear();
315     angles.push_back( a1);
316     angles.push_back( RS_Math::correctAngle( a1 + M_PI));
317     angles.push_back( a2);
318     angles.push_back( RS_Math::correctAngle( a2 + M_PI));
319     std::sort( angles.begin(), angles.end());
320 
321     // find starting quadrant and compute the offset for quadrant() method
322     int startQuadrant = 0;
323     for( auto angle : angles) {
324         if( RS_Math::equal( a1, angle)) {
325             break;
326         }
327         ++startQuadrant;
328     }
329     quadrantOffset = 0x03 & (4 - startQuadrant);
330 }
331 
332 /**
333  * Find the quadrant of \p angle relative to 1st quadrant.
334  * When the angle lines are selected, the starting quadrant
335  * is shifted to become 0 by \p quadrantOffset.
336  * This is the criterion how the angles dimension is drawn.
337  *
338  * @param angle The angle, e.g. mouse or coordinate position
339  * @return The quadrant of \p angle, relative to the 1st selection quadrant
340  */
quadrant(const double angle)341 int RS_ActionDimAngular::quadrant(const double angle)
342 {
343     if( 1 > angles.size()) {
344         return 0;
345     }
346 
347     double a1 {RS_Math::correctAngle2( angles.at(0) - angle)};
348     double a2 {RS_Math::correctAngle2( angles.at(1) - angle)};
349 
350     int angleQuadrant {0};
351     if( 0.0 < a1 && 0.0 < a2) {
352         angleQuadrant = 3;
353     }
354     else if( 0.0 >= a1 && 0.0 >= a2) {
355         angleQuadrant = 1;
356     }
357     else if( 0.0 < a1 && 0.0 >= a2) {
358         angleQuadrant = 2;
359     }
360 
361     return (0x03 & (angleQuadrant + quadrantOffset));
362 }
363 
364 /**
365  * On \p mouseMoveEvent, \p mouseReleaseEvent and \p coordinateEvent
366  * this method sets the dimension data appropriate to the mouse
367  * cursor/coordinate in \p dimPos.
368  * When \p calcCenter is true, the intersection point and other static
369  * values are computed. This is only necessary, when line selection changes,
370  * e.g. on \p mouseReleaseEvent. For \p mouseMoveEvent calcCenter is false.
371  *
372  * @param dimPos The mouse/coordinate position
373  * @param calcCenter If true, the center and corresponding values are calculated
374  * @return true If the dimension data were set, false is a parameter is invalid
375  */
setData(const RS_Vector & dimPos,const bool calcCenter)376 bool RS_ActionDimAngular::setData(const RS_Vector &dimPos, const bool calcCenter /*= false*/)
377 {
378     if( ! line1.getStartpoint().valid || ! line2.getStartpoint().valid) {
379         return false;
380     }
381 
382     if ( ! center.valid || calcCenter) {
383         RS_VectorSolutions sol = RS_Information::getIntersectionLineLine( &line1, &line2);
384         center = sol.get(0);
385     }
386     if ( ! center.valid) {
387         return false;
388     }
389 
390     if( calcCenter) {
391         justify( line1, click1);
392         justify( line2, click2);
393         lineOrder( dimPos);
394     }
395 
396     edata->definitionPoint4 = dimPos;
397     switch( quadrant( (dimPos - center).angle())) {
398     default:
399     case 0:
400         edata->definitionPoint1 = line1.getEndpoint();
401         edata->definitionPoint2 = line1.getStartpoint();
402         edata->definitionPoint3 = line2.getEndpoint();
403         data->definitionPoint   = line2.getStartpoint();
404         break;
405 
406     case 1:
407         edata->definitionPoint1 = line2.getEndpoint();
408         edata->definitionPoint2 = line2.getStartpoint();
409         edata->definitionPoint3 = line1.getStartpoint();
410         data->definitionPoint   = line1.getEndpoint();
411         break;
412 
413     case 2:
414         edata->definitionPoint1 = line2.getEndpoint();
415         edata->definitionPoint2 = line2.getStartpoint();
416         edata->definitionPoint3 = line1.getEndpoint();
417         data->definitionPoint   = line1.getStartpoint();
418         break;
419 
420     case 3:
421         edata->definitionPoint1 = line2.getStartpoint();
422         edata->definitionPoint2 = line2.getEndpoint();
423         edata->definitionPoint3 = line1.getEndpoint();
424         data->definitionPoint   = line1.getStartpoint();
425         break;
426     }
427 
428     return true;
429 }
430 
431 // EOF
432