]> Shamusworld >> Repos - architektonas/blob - src/arc.cpp
Added 1st stab at grouping capability.
[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 //
14
15 #include "arc.h"
16
17 #include <QtGui>
18 #include "mathconstants.h"
19 #include "painter.h"
20
21
22 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
23         Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2)
24 {
25         // This is in the base class, why can't we use the contructor to fill it???
26         type = OTArc;
27 }
28
29
30 Arc::~Arc()
31 {
32 }
33
34
35 /*virtual*/ void Arc::Draw(Painter * painter)
36 {
37         QPen pen;
38
39         if (state == OSSelected)
40         {
41                 Point p1(cos(startAngle), sin(startAngle));
42                 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
43                 Vector handle2 = (p1 * radius) + position;
44                 Vector handle3 = (p2 * radius) + position;
45
46                 if ((hitHandle2 || hitHandle3) && objectWasDragged)
47                 {
48                         if (hitHandle2)
49                         {
50                                 // If we rotating, we draw a guideline showing the angle we're
51                                 // moving it from.
52                                 Point p3(cos(oldAngle), sin(oldAngle));
53                                 Vector oldLine = (p3 * (radius * 1.25)) + position;
54                                 pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
55                                 painter->SetPen(pen);
56                                 painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
57                         }
58
59                         // In rotating and setting the span, we draw a line showing where
60                         // we angle/span is that we're setting.
61                         pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
62                         painter->SetPen(pen);
63                         painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
64                 }
65
66                 // Draw the center point of the arc
67                 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
68                 painter->DrawHandle(position);
69
70                 // Draw the rotation & span setting handles
71                 painter->DrawHandle(handle2);
72                 painter->DrawHandle(handle3);
73
74                 // If we're rotating or setting the span, draw an information panel
75                 // showing both absolute and relative angles being set.
76                 if ((hitHandle2 || hitHandle3 || hitHandle4) && objectWasDragged)
77                 {
78                         double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
79                         double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
80                                 startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
81
82                         QString text;
83
84                         if (hitHandle2)
85                         {
86                                 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
87                                         + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
88                                 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
89                         }
90                         else if (hitHandle3)
91                         {
92                                 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
93                                         + QObject::tr("\nSpan: %2") + QChar(0x00B0);
94                                 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
95                         }
96                         else if (hitHandle4)
97                         {
98                                 text = QObject::tr("Radius: %1\nScale: %2%");
99                                 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
100                         }
101
102                         pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
103                         painter->SetPen(pen);
104                         painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
105                         QRectF textRect(10.0, 10.0, 260.0, 60.0);       // x, y, w, h
106                         painter->DrawRoundedRect(textRect, 7.0, 7.0);
107
108                         textRect.setLeft(textRect.left() + 14);
109                         painter->SetFont(*Object::font);
110                         pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine);
111                         painter->SetPen(pen);
112                         painter->DrawText(textRect, Qt::AlignVCenter, text);
113                 }
114         }
115         else
116         {
117                 pen = QPen(Qt::black, 1.0, Qt::SolidLine);
118                 painter->SetPen(pen);
119         }
120
121         painter->DrawArc(position, radius, startAngle, angleSpan);
122 }
123
124
125 /*virtual*/ Vector Arc::Center(void)
126 {
127         return position;
128 }
129
130
131 /*
132  We need at least *four* handles for this object:
133  - one for moving
134  - one for resizing
135  - one for rotation
136  - one for setting the span of the arc
137
138 We need to think about the intuitive way (if there is any) to grab and
139 manipulate a complex object like this... Need to think, "What should happen when
140 I click here and drag there?"
141
142 Also: should put the snap logic into the Object base class (as a static method)...
143 */
144
145 /*virtual*/ bool Arc::Collided(Vector point)
146 {
147         objectWasDragged = false;
148         Vector v1 = point - position;                   // Head minus tail (vector points at "point")
149
150         // Check for collision with various things...
151         hitHandle1 = false;     // Moving
152         hitHandle2 = false;     // Rotation
153         hitHandle3 = false;     // Setting span of the arc
154         hitHandle4 = false;     // Resizing
155 /*
156 What we have:
157 the center of the arc
158 the starting angle
159 the span of the arc
160 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
161 This vector is already unitized, so all we need to do to get our point is to multiply it by
162 radius (to get the length correct) and add it to the center point (to get the correct position).
163 */
164         Point p1(cos(startAngle), sin(startAngle));
165         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
166         Vector handle2 = (p1 * radius) + position;
167         Vector handle3 = (p2 * radius) + position;
168         double pointerAngle = v1.Angle();
169
170 #if 1
171         // Center handle
172         if (v1.Magnitude() < 10.0)
173                 hitHandle1 = true;
174         // Span handle
175         else if (Vector(handle3 - point).Magnitude() < 10.0)
176                 hitHandle3 = true;
177         // Rotate handle
178         else if (Vector(handle2 - point).Magnitude() < 10.0)
179                 hitHandle2 = true;
180         // Resize handle (the arc itself)
181         else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
182                 && AngleInArcSpan(pointerAngle))
183                 hitHandle4 = true;
184 #endif
185
186 /*
187 State Management:
188 We want the arc to go into OSSelected mode if we click on it but don't drag.
189 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
190 Otherwise, we hit a handle and we want to modify the object whether it's in
191 OSSelected mode or not.
192
193 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
194 until we select it.
195         However, if dragging, revert to I once finished.
196
197 If S, stay in S. Can drag all.
198
199 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
200 How can we be sure?
201
202 So. We have a matrix that looks like this:
203
204         |Was I|Was S
205 --------+-----+-----
206 Inactive|     |   
207 --------+-----+-----
208 Selected|     |   
209
210         |h1|h2|h3|h4
211 --------+--+--+--+--
212 Inactive|  |  |  |
213 --------+--+--+--+--
214 Selected|  |  |  |
215
216 so let's do like this:
217 */
218         if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
219         {
220                 oldState = state;
221                 state = OSSelected;
222 //              oldPoint = point;
223                 oldPoint = position;
224                 oldAngle = startAngle;
225                 oldRadius = radius;
226
227                 return true;
228         }
229
230         state = OSInactive;
231         return false;
232 }
233
234
235 /*virtual*/ void Arc::PointerMoved(Vector point)
236 {
237         // The TLC will send these messages if the object is selected but not clicked on.
238         // So we have to be careful with our assumptions here.
239         // This is actually untrue in that case, we need to come up with something better
240         // here...
241         objectWasDragged = true;
242         needUpdate = false;
243
244         if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
245                 return;
246
247         Vector delta = point - oldPoint;
248
249         if (hitHandle1)                 // Move arc
250         {
251                 position += delta;
252         }
253         else if (hitHandle2)    // Rotate arc
254         {
255                 startAngle = Vector(point - position).Angle();
256         }
257         else if (hitHandle3)    // Set arc span
258         {
259                 double angle = Vector(point - position).Angle();
260
261                 if (angle < startAngle)
262                         angle += 2.0 * PI;
263
264                 angleSpan = angle - startAngle;
265         }
266         else if (hitHandle4)    // Resize the radius of the arc
267         {
268                 radius = Vector(point - position).Magnitude();
269         }
270
271         oldPoint = point;
272         needUpdate = true;
273 }
274
275
276 /*virtual*/ void Arc::PointerReleased(void)
277 {
278         hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
279         // Here we check for just a click: If object was clicked and dragged, then
280         // revert to the old state (OSInactive). Otherwise, keep the new state that
281         // we set.
282 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
283 about keeping track of old states...
284 Well, we can't know if it was dragged from Inactive or not, that's the problem. We could make
285 a variable called "needToRevertToInactive" instead
286
287 I mean, we could write like:
288         if (objectWasDragged && oldState == OSInactive)
289                 state = OSInactive;
290
291 but this is actually more compact and cleaner.
292 */
293         if (objectWasDragged)
294                 state = oldState;
295 }
296
297
298 /*virtual*/ QRectF Arc::Extents(void)
299 {
300 #warning "!!! Arc extents not calculated !!!"
301         return QRectF();
302 }
303
304
305 #if 0
306 /*virtual*/ bool Arc::NeedsUpdate(void)
307 {
308         return needUpdate;
309 }
310 #endif
311
312
313 #if 0
314 /*virtual*/ ObjectType Arc::Type(void)
315 {
316         return OTArc;
317 }
318 #endif
319
320
321 /*
322 start = 350, span = 20, end = 10, angle = 5
323 angle < start, so angle = 365
324 */
325 bool Arc::AngleInArcSpan(double angle)
326 {
327         // This should be simple except for a small complication: The start angle plus
328         // the angle span can end up being less than the start angle! So, to check
329         // for this possibility, we check to see if the angle passed in is less than
330         // the start angle and if so, add 2PI to the angle passed in. Then we take the
331         // difference between our start angle and this adjusted angle and see if it
332         // falls within our span.
333         if (angle < startAngle)
334                 angle += 2.0 * PI;
335
336         double passedInSpan = angle - startAngle;
337
338         return (passedInSpan <= angleSpan ?  true : false);
339 }
340
341
342 /*virtual*/ void Arc::Enumerate(FILE * file)
343 {
344         fprintf(file, "ARC (%lf,%lf) %lf, %lf, %lf\n", position.x, position.y, radius, startAngle, angleSpan);
345 }
346