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