]> Shamusworld >> Repos - architektonas/blob - src/container.cpp
Various fixes to Container/Group handling, added DrawArcAction.
[architektonas] / src / container.cpp
1 // container.cpp: Container 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  06/02/2011  Added code to delete objects in this container when they go
13 //                  out of scope
14 //
15
16 #include "container.h"
17
18 #include <QtGui>
19 #include "dimension.h"
20 #include "painter.h"
21
22
23 Container::Container(Vector p1, Object * p/*= NULL*/): Object(p1, p),
24         isTopLevelContainer(false),
25         dragging(false), draggingHandle1(false), draggingHandle2(false)//, needUpdate(false)
26 {
27         type = OTContainer;
28         state = OSInactive;
29 }
30
31
32 // Copy constructor
33 Container::Container(const Container & copy): Object(copy.position, copy.parent)
34 {
35         // Use overloaded assignment operator
36         *this = copy;
37         type = OTContainer;
38         state = OSInactive;
39 }
40
41
42 Container::~Container()
43 {
44         Clear();
45 }
46
47
48 // Assignment operator
49 Container & Container::operator=(const Container & from)
50 {
51         // Take care of self-assignment
52         if (this == &from)
53                 return *this;
54
55         Clear();
56
57         // Small problem with this approach: if the copied object goes out of scope,
58         // all of the objects we copied in here will be deleted. D'oh!
59         for(uint i=0; i<from.objects.size(); i++)
60         {
61                 Object * object = from.objects[i];
62
63                 // Need to copy the object here...
64
65                 objects.push_back(object);
66         }
67
68         return *this;
69 }
70
71
72 /*virtual*/ void Container::Draw(Painter * painter)
73 {
74         QRectF boundary;
75
76         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
77         {
78                 (*i)->Draw(painter);
79                 boundary = boundary.united((*i)->Extents());
80         }
81
82         if ((state == OSSelected) || hit)
83         {
84                 if (hit)
85                         painter->SetPen(QPen(Qt::magenta, 1.0, Qt::DashLine));
86                 else
87                         painter->SetPen(QPen(Qt::blue, 2.0, Qt::DashLine));
88
89                 painter->SetBrush(QBrush(Qt::NoBrush));
90                 painter->DrawPaddedRect(boundary);
91         }
92 }
93
94
95 /*virtual*/ Vector Container::Center(void)
96 {
97         return position;
98 }
99
100
101 /*
102  We need at least *three* handles for this object:
103  - one for moving
104  - one for resizing
105  - one for rotation
106
107 We need to think about the intuitive way (if there is any) to grab and
108 manipulate a complex object like this... Need to think, "What should happen when
109 I click here and drag there?"
110
111 Also: should put the snap logic into the Object base class (as a static method)...
112 */
113
114
115 // Need to add checking here for clicking on a member of a group (Container),
116 // and checking for if it's a top level container (the DrawingView's document).
117 /*
118 One approach is to check for the parent of the container: If it's NULL, then it's
119 the DrawingView's document. It might be better, though, to set a boolean like
120 isTopLevelContainer so that we can do things like edit members of a group without
121 having to ungroup them first (like Inkscape).
122 */
123 /*virtual*/ bool Container::Collided(Vector point)
124 {
125         objectWasDragged = false;
126 //      Vector v1 = position - point;
127
128         bool collision = false;
129
130         // NOTE that this deletes the object on mouse down instead of mouse up. Have to
131         // check to see how it feels to do it that way...
132         if (deleteActive)
133         {
134                 for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end();)
135                 {
136                         if ((*i)->Collided(point))
137                         {
138 printf("Container::Collided: Deleting object ($%X)\n", *i);
139                                 Object * objectToDelete = *i;
140                                 objects.erase(i);
141                                 delete objectToDelete;
142                         }
143                         else
144                                 i++;
145                 }
146         }
147         else
148         {
149                 for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
150                 {
151                         if ((*i)->Collided(point))
152                                 collision = true;
153                 }
154         }
155
156         // We check to see if the container we're trying to access is the
157         // DrawingView's document. If so, we ignore the state of the container.
158         // Otherwise, we care about the state of the container. :-)
159         if (isTopLevelContainer)
160                 state = OSInactive;
161         else
162         {
163                 state = (collision ? OSSelected : OSInactive);
164
165                 if (state == OSSelected)
166                 {
167                         DeselectAll();
168                         dragging = true;
169                         oldPoint = point;
170                 }
171         }
172
173         return collision;
174 }
175
176 /*
177 What we need to do is check for whether or not we're a top level container,
178 and override the passing of stuff into the objects held. So here, if we're *NOT*
179 a top level container, instead of passing PointerMoved to our contained objects,
180 we check to see if our bounds are met (for selection rectangle, e.g.).
181
182 Also, for things like being able to split point's hot spots we need to be able
183 to check for that crap in the top level container. Which means that objects can
184 still know how to move themselves, but they can also defer to their container
185 as well. Which also means that things like HitTest() need to be in the Object
186 class so that we can leverage that stuff here as well.
187 */
188
189 // The TLC is passing all mouse movement here, so we're doing the same here.
190 // Need to adjust all other objects to handle things correctly.
191
192 // One optimization that will need to be done eventually is to subdivide the screen
193 // into parts and keep subdividing until an acceptable number of objects lie within
194 // the slice. This way, the GUI will still be responsive and *not* have to test
195 // every object for collision.
196 /*virtual*/ void Container::PointerMoved(Vector point)
197 {
198         std::vector<Object *>::iterator i;
199
200         if (!isTopLevelContainer)
201         {
202                 // check for selection rectangle too
203                 if (selectionInProgress)
204                 {
205                         if (selection.contains(Extents()))
206                                 state = OSSelected;
207                         else
208                                 state = OSInactive;
209
210                         return;
211                 }
212
213                 // No need to do any checking if we're already selected...
214 //              if (state == OSSelected)
215 //                      return;
216
217 //              oldState = state;
218 //              needUpdate = true;
219 //              if (dragging &&
220                 bool oldHit = hit;
221                 hit = false;
222
223                 for(i=objects.begin(); i!=objects.end(); i++)
224                 {
225                         if ((*i)->HitTest(point))
226                         {
227 //                              state = OSSelected;
228 //                              return;
229                                 hit = true;
230                                 break;
231                         }
232                 }
233
234                 needUpdate = (oldHit != hit ? true : false);
235 //              state = oldState;
236
237                 if (dragging)
238                 {
239                         Vector delta = point - oldPoint;
240
241                         for(i=objects.begin(); i!=objects.end(); i++)
242                                 (*i)->Translate(delta);
243
244                         oldPoint = point;
245                         needUpdate = true;
246                 }
247
248                 return;
249         }
250
251         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
252         {
253 //              if (objects[i]->GetState() == OSSelected)
254                 (*i)->PointerMoved(point);
255         }
256
257         // Generic container doesn't need this???
258 //      needUpdate = false;
259 }
260
261
262 /*virtual*/ void Container::PointerReleased(void)
263 {
264         if (!isTopLevelContainer)
265         {
266                 dragging = false;
267                 return;
268         }
269 #if 0
270         dragging = false;
271         draggingHandle1 = false;
272         draggingHandle2 = false;
273
274         // Here we check for just a click: If object was clicked and dragged, then
275         // revert to the old state (OSInactive). Otherwise, keep the new state that
276         // we set.
277 /*
278 Maybe it would be better to just check for "object was dragged" state and not
279 have to worry about keeping track of old states...
280 */
281         if (objectWasDragged)
282                 state = oldState;
283 //Note that the preceeding is unnecessary for a generic container!
284 #endif
285
286 //      for(int i=0; i<(int)objects.size(); i++)
287 //              objects[i]->PointerReleased();
288         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
289                 (*i)->PointerReleased();
290 }
291
292
293 /*virtual*/ bool Container::NeedsUpdate(void)
294 {
295         // If this is *not* a top level container, then we treat it as an
296         // aggregate object.
297         if (!isTopLevelContainer)
298         {
299                 return needUpdate;
300         }
301
302         // Search through objects for one that needs an update; if one is found,
303         // return immediately.
304         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
305         {
306                 if ((*i)->NeedsUpdate())
307                         return true;
308         }
309
310         return false;
311 }
312
313
314 /*virtual*/ void Container::Add(Object * object)
315 {
316         objects.push_back(object);
317 printf("Container: Added object (=$%X). size = %li\n", object, objects.size());
318 }
319
320
321 /*virtual*/ QRectF Container::Extents(void)
322 {
323         QRectF bounds;
324
325         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
326                 bounds = bounds.united((*i)->Extents());
327
328         return bounds;
329 }
330
331
332 void Container::Delete(Object * objectToDelete)
333 {
334         std::vector<Object *>::iterator i = objects.begin();
335
336         while (i != objects.end())
337         {
338                 if (*i == objectToDelete)
339                 {
340                         objects.erase(i);
341                         delete objectToDelete;
342                         return;
343                 }
344
345                 i++;
346         }
347 }
348
349
350 void Container::DeleteSelectedItems(void)
351 {
352         std::vector<Object *>::iterator i = objects.begin();
353
354         while (i != objects.end())
355         {
356                 if ((*i)->state == OSSelected)
357                 {
358                         delete *i;
359                         objects.erase(i);
360                 }
361                 else
362                         i++;
363         }
364 }
365
366
367 void Container::Clear(void)
368 {
369         std::vector<Object *>::iterator i = objects.begin();
370
371         while (i != objects.end())
372         {
373 printf("Container: Deleting object ($%X)...\n", *i);
374                 delete (*i);
375                 objects.erase(i);
376         }
377 }
378
379
380 void Container::SelectAll(void)
381 {
382         for(unsigned int i=0; i<objects.size(); i++)
383                 objects[i]->state = OSSelected;
384 }
385
386
387 void Container::DeselectAll(void)
388 {
389         for(unsigned int i=0; i<objects.size(); i++)
390                 objects[i]->state = OSInactive;
391 }
392
393
394 int Container::ItemsSelected(void)
395 {
396         int selected = 0;
397
398         for(uint i=0; i<objects.size(); i++)
399                 if (objects[i]->state == OSSelected)
400                         selected++;
401
402         return selected;
403 }
404
405
406 Object * Container::SelectedItem(unsigned int index)
407 {
408         unsigned int selectedIndex = 0;
409
410         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
411         {
412                 if ((*i)->state == OSSelected)
413                 {
414                         if (selectedIndex == index)
415                                 return *i;
416                         else
417                                 selectedIndex++;
418                 }
419         }
420
421         return NULL;
422 }
423
424
425 void Container::MoveContentsTo(Container * newContainer)
426 {
427         // Sanity check
428         if (newContainer == NULL)
429                 return;
430
431         // Shuffle the contents of this container to the new one
432 //      for(unsigned int i=0; i<objects.size(); i++)
433         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
434         {
435                 newContainer->Add(*i);
436                 (*i)->Reparent(newContainer);
437         }
438
439         // & clear our vector
440         objects.clear();
441 }
442
443
444 void Container::MoveSelectedContentsTo(Container * newContainer)
445 {
446         // Sanity check
447         if (newContainer == NULL)
448                 return;
449
450         // Shuffle the contents of this container to the new one
451         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end();)
452         {
453                 if ((*i)->state != OSSelected)
454                 {
455                         i++;
456                         continue;
457                 }
458
459                 newContainer->Add(*i);
460                 (*i)->Reparent(newContainer);
461                 objects.erase(i);
462         }
463 }
464
465
466 void Container::ResizeAllDimensions(double newSize)
467 {
468         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
469         {
470                 if ((*i)->type == OTDimension)
471                         ((Dimension *)(*i))->size = newSize;
472                 if ((*i)->type == OTContainer)
473                         ((Container *)(*i))->ResizeAllDimensions(newSize);
474         }
475 }
476
477
478 /*virtual*/ void Container::Enumerate(FILE * file)
479 {
480         // Only put "CONTAINER" markers if *not* the top level container
481 //      if (parent != NULL)
482         if (!isTopLevelContainer)
483                 fprintf(file, "CONTAINER\n");
484
485         for(uint i=0; i<objects.size(); i++)
486                 objects[i]->Enumerate(file);
487
488 //      if (parent != NULL)
489         if (!isTopLevelContainer)
490                 fprintf(file, "ENDCONTAINER\n");
491 }
492