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