]> Shamusworld >> Repos - architektonas/blob - src/arc.cpp
Added layer attribute to load/save code.
[architektonas] / src / arc.cpp
1 // arc.cpp: Arc object
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  03/30/2011  Created this file
12 // JLH  04/03/2011  Added information panel (angles) rendering
13 // JLH  08/16/2013  Added continuous user feedack like for Line and Circle
14 //
15
16 #include "arc.h"
17
18 #include <QtGui>
19 #include "mathconstants.h"
20 #include "painter.h"
21
22
23 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
24         Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2),
25         draggingCenter(false), draggingEdge(false), draggingRotate(false),
26         draggingSpan(false),
27         hitCenter(false), hitArc(false), hitRotate(false), hitSpan(false)
28 {
29         // This is in the base class, why can't we use the contructor to fill it???
30         type = OTArc;
31         state = OSInactive;
32 }
33
34
35 Arc::~Arc()
36 {
37 }
38
39
40 /*virtual*/ void Arc::Draw(Painter * painter)
41 {
42         QPen pen;
43         painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
44
45         Point p1(cos(startAngle), sin(startAngle));
46         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
47         Vector handle2 = (p1 * radius) + position;
48         Vector handle3 = (p2 * radius) + position;
49
50         if ((state == OSSelected) || ((state == OSInactive) && hitRotate))
51                 painter->DrawHandle(handle2);
52
53         if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
54                 painter->DrawHandle(handle3);
55
56         if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
57                 painter->DrawHandle(position);
58
59         if ((state == OSInactive) && !hitArc)
60                 painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
61
62         painter->DrawArc(position, radius, startAngle, angleSpan);
63
64         if (draggingRotate || draggingSpan)
65         {
66                 if (draggingRotate)
67                 {
68                         // If we rotating, we draw a guideline showing the angle we're
69                         // moving it from.
70                         Point p3(cos(oldAngle), sin(oldAngle));
71                         Vector oldLine = (p3 * (radius * 1.25)) + position;
72                         pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
73                         painter->SetPen(pen);
74 //                      painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
75                         painter->DrawLine(position, oldLine);
76                 }
77
78                 // In rotating and setting the span, we draw a line showing where
79                 // we angle/span is that we're setting.
80                 pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
81                 painter->SetPen(pen);
82                 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
83         }
84
85         // If we're rotating or setting the span, draw an information panel
86         // showing both absolute and relative angles being set.
87         if (draggingRotate || draggingSpan || draggingEdge)
88         {
89                 double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
90                 double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
91                         startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
92
93                 QString text;
94
95                 if (draggingRotate)
96                 {
97                         text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
98                                 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
99                         text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
100                 }
101                 else if (draggingSpan)
102                 {
103                         text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
104                                 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
105                         text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
106                 }
107                 else if (draggingEdge)
108                 {
109                         text = QObject::tr("Radius: %1\nScale: %2%");
110                         text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
111                 }
112
113 #if 0
114                 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
115                 painter->SetPen(pen);
116                 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
117                 QRectF textRect(10.0, 10.0, 270.0, 70.0);       // x, y, w, h
118                 painter->DrawRoundedRect(textRect, 7.0, 7.0);
119
120                 textRect.setLeft(textRect.left() + 14);
121                 painter->SetFont(*Object::font);
122                 pen = QPen(QColor(0x00, 0x5F, 0xDF));
123                 painter->SetPen(pen);
124                 painter->DrawText(textRect, Qt::AlignVCenter, text);
125 #else
126                 painter->DrawInformativeText(text);
127 #endif
128         }
129 }
130
131
132 /*virtual*/ Vector Arc::Center(void)
133 {
134         return position;
135 }
136
137
138 /*
139  We need at least *four* handles for this object:
140  - one for moving
141  - one for resizing
142  - one for rotation
143  - one for setting the span of the arc
144
145 We need to think about the intuitive way (if there is any) to grab and
146 manipulate a complex object like this... Need to think, "What should happen when
147 I click here and drag there?"
148
149 Also: should put the snap logic into the Object base class (as a static method)...
150 */
151
152 /*virtual*/ bool Arc::Collided(Vector point)
153 {
154         objectWasDragged = false;
155         bool hitSomething = HitTest(point);
156         draggingCenter = hitCenter;
157         draggingEdge   = hitArc;
158         draggingRotate = hitRotate;
159         draggingSpan   = hitSpan;
160
161         // Now that we've done our hit testing on the non-snapped point, snap it if
162         // necessary...
163         if (snapToGrid)
164                 point = SnapPointToGrid(point);
165
166 /*
167 State Management:
168 We want the arc to go into OSSelected mode if we click on it but don't drag.
169 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
170 Otherwise, we hit a handle and we want to modify the object whether it's in
171 OSSelected mode or not.
172
173 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
174 until we select it.
175         However, if dragging, revert to I once finished.
176
177 If S, stay in S. Can drag all.
178
179 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
180 How can we be sure?
181
182 So. We have a matrix that looks like this:
183
184         |Was I|Was S
185 --------+-----+-----
186 Inactive|     |   
187 --------+-----+-----
188 Selected|     |   
189
190         |h1|h2|h3|h4
191 --------+--+--+--+--
192 Inactive|  |  |  |
193 --------+--+--+--+--
194 Selected|  |  |  |
195
196 so let's do like this:
197 */
198         if (hitSomething)
199         {
200                 oldState = state;
201                 state = OSSelected;
202 //              oldPoint = point;
203                 oldPoint = position;
204                 oldAngle = startAngle;
205                 oldRadius = radius;
206                 return true;
207         }
208
209         state = OSInactive;
210         return false;
211 }
212
213
214 /*virtual*/ void Arc::PointerMoved(Vector point)
215 {
216 // one other thing to check here for is if a modifier key is being held as well,
217 // to allow for multi-selection
218         if (selectionInProgress)
219         {
220                 // Check for whether or not the rect contains this circle
221 //              if (selection.normalized().contains(Extents()))
222                 if (selection.contains(Extents()))
223                         state = OSSelected;
224                 else
225                         state = OSInactive;
226
227                 return;
228         }
229
230         // The TLC will send these messages if the object is selected but not clicked on.
231         // So we have to be careful with our assumptions here.
232         // This is actually untrue in that case, we need to come up with something better
233         // here...
234 //      objectWasDragged = true;
235 //      needUpdate = false;
236         SaveHitState();
237         HitTest(point);
238         needUpdate = HitStateChanged();
239         objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
240
241         if (objectWasDragged)
242                 needUpdate = true;
243 //      if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
244 //              return;
245
246 //      Vector delta = point - oldPoint;
247
248         if (draggingCenter)
249                 position = point;
250         else if (draggingEdge)
251                 radius = Vector::Magnitude(point, position);
252         else if (draggingRotate)
253         {
254                 startAngle = Vector(point - position).Angle();
255         }
256         else if (draggingSpan)
257         {
258                 double angle = Vector(point - position).Angle();
259
260                 if (angle < startAngle)
261                         angle += 2.0 * PI;
262
263                 angleSpan = angle - startAngle;
264         }
265
266         // Why save this? For rendering code?
267         oldPoint = point;
268 //      needUpdate = true;
269 }
270
271
272 /*virtual*/ void Arc::PointerReleased(void)
273 {
274         // Mouse went up, so our dragging is done (if any *was* done, that is)
275 //      hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
276         draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
277         hitCenter = hitArc = hitRotate = hitSpan = false;
278
279         // If the object was dragged, then revert to the old state.
280         // Otherwise, we were probably just clicked, and want to stay in the selected state.
281         if (objectWasDragged)
282                 state = oldState;
283 }
284
285
286 /*virtual*/ bool Arc::HitTest(Point point)
287 {
288         hitCenter = hitArc = hitRotate = hitSpan = false;
289
290 /*
291 What we have:
292 the center of the arc
293 the starting angle
294 the span of the arc
295 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
296 This vector is already unitized, so all we need to do to get our point is to
297 multiply it by radius (to get the length correct) and add it to the center
298 point (to get the correct position).
299 */
300         Vector v1(point, position);     // Head minus tail (vector points at "point")
301         Point p1(cos(startAngle), sin(startAngle));
302         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
303         Vector handle2 = (p1 * radius) + position;
304         Vector handle3 = (p2 * radius) + position;
305         double pointerAngle = v1.Angle();
306         double length = v1.Magnitude();
307
308 #if 0
309         if (v1.Magnitude() < 10.0)
310                 hitCenter = true;
311         else if (Vector(handle3 - point).Magnitude() < 10.0)
312                 hitSpan = true;
313         else if (Vector(handle2 - point).Magnitude() < 10.0)
314                 hitRotate = true;
315         else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
316                 && AngleInArcSpan(pointerAngle))
317                 hitArc = true;
318 #else
319         if ((length * Painter::zoom) < 8.0)
320                 hitCenter = true;
321         else if (((fabs(length - radius) * Painter::zoom) < 2.0)
322                 && AngleInArcSpan(pointerAngle))
323                 hitArc = true;
324         else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
325                 hitRotate = true;
326         else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
327                 hitSpan = true;
328 #endif
329
330         return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
331 }
332
333
334 /*virtual*/ QRectF Arc::Extents(void)
335 {
336         double start = startAngle;
337         double end = start + angleSpan;
338         QPointF p1(cos(start), sin(start));
339         QPointF p2(cos(end), sin(end));
340         QRectF bounds(p1, p2);
341
342         // Swap X/Y coordinates if they're backwards...
343         if (bounds.left() > bounds.right())
344         {
345                 double temp = bounds.left();
346                 bounds.setLeft(bounds.right());
347                 bounds.setRight(temp);
348         }
349
350         if (bounds.bottom() > bounds.top())
351         {
352                 double temp = bounds.bottom();
353                 bounds.setBottom(bounds.top());
354                 bounds.setTop(temp);
355         }
356
357         // If the end of the arc is before the beginning, add 360 degrees to it
358         if (end < start)
359                 end += 2.0 * PI;
360
361         // Adjust the bounds depending on which axes are crossed
362         if ((start < PI_OVER_2) && (end > PI_OVER_2))
363                 bounds.setTop(1.0);
364
365         if ((start < PI) && (end > PI))
366                 bounds.setLeft(-1.0);
367
368         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
369                 bounds.setBottom(-1.0);
370
371         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
372                 bounds.setRight(1.0);
373
374         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
375                 bounds.setTop(1.0);
376
377         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
378                 bounds.setLeft(-1.0);
379
380         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
381                 bounds.setBottom(-1.0);
382
383         bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
384         bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
385         bounds.translate(position.x, position.y);
386         return bounds;
387 }
388
389
390 /*
391 start = 350, span = 20, end = 10, angle = 5
392 angle < start, so angle = 365
393 */
394 bool Arc::AngleInArcSpan(double angle)
395 {
396         // This should be simple except for a small complication: The start angle plus
397         // the angle span can end up being less than the start angle! So, to check
398         // for this possibility, we check to see if the angle passed in is less than
399         // the start angle and if so, add 2PI to the angle passed in. Then we take the
400         // difference between our start angle and this adjusted angle and see if it
401         // falls within our span.
402         if (angle < startAngle)
403                 angle += 2.0 * PI;
404
405         double passedInSpan = angle - startAngle;
406
407         return (passedInSpan <= angleSpan ?  true : false);
408 }
409
410
411 void Arc::SaveHitState(void)
412 {
413         oldHitCenter = hitCenter;
414         oldHitArc    = hitArc;
415         oldHitRotate = hitRotate;
416         oldHitSpan   = hitSpan;
417 }
418
419
420 bool Arc::HitStateChanged(void)
421 {
422         if ((hitCenter != oldHitCenter)
423                 || (hitArc != oldHitArc)
424                 || (hitRotate != oldHitRotate)
425                 || (hitSpan != oldHitSpan))
426                 return true;
427
428         return false;
429 }
430
431
432 /*virtual*/ void Arc::Enumerate(FILE * file)
433 {
434         fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf\n", layer, position.x, position.y, radius, startAngle, angleSpan);
435 }
436
437
438 /*virtual*/ Object * Arc::Copy(void)
439 {
440 #warning "!!! This doesn't take care of attached Dimensions !!!"
441 /*
442 This is a real problem. While having a pointer in the Dimension to this line's points
443 is fast & easy, it creates a huge problem when trying to replicate an object like this.
444
445 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
446 way, if you copy them, ... you might still have problems. Because you can't be sure if
447 a copy will be persistant or not, you then *definitely* do not want them to have the
448 same reference number.
449 */
450         return new Arc(position, radius, startAngle, angleSpan, parent);
451 }
452