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