]> Shamusworld >> Repos - architektonas/blob - src/container.cpp
ced7326de4979b91e67d4d16dfe97694101d4837
[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         // We check to see if the container we're trying to access is the
166         // DrawingView's document. If so, we ignore the state of the container.
167         // Otherwise, we care about the state of the container. :-)
168         if (isTopLevelContainer)
169                 state = OSInactive;
170         else
171         {
172                 state = (collision ? OSSelected : OSInactive);
173
174                 if (state == OSSelected)
175                 {
176                         DeselectAll();
177                         dragging = true;
178                         oldPoint = point;
179                 }
180         }
181
182         return collision;
183 }
184
185 /*
186 What we need to do is check for whether or not we're a top level container,
187 and override the passing of stuff into the objects held. So here, if we're *NOT*
188 a top level container, instead of passing PointerMoved to our contained objects,
189 we check to see if our bounds are met (for selection rectangle, e.g.).
190
191 Also, for things like being able to split point's hot spots we need to be able
192 to check for that crap in the top level container. Which means that objects can
193 still know how to move themselves, but they can also defer to their container
194 as well. Which also means that things like HitTest() need to be in the Object
195 class so that we can leverage that stuff here as well.
196 */
197
198 // The TLC is passing all mouse movement here, so we're doing the same here.
199 // Need to adjust all other objects to handle things correctly.
200
201 // One optimization that will need to be done eventually is to subdivide the screen
202 // into parts and keep subdividing until an acceptable number of objects lie within
203 // the slice. This way, the GUI will still be responsive and *not* have to test
204 // every object for collision.
205 /*virtual*/ void Container::PointerMoved(Vector point)
206 {
207         std::vector<Object *>::iterator i;
208
209         if (!isTopLevelContainer)
210         {
211                 // check for selection rectangle too
212                 if (selectionInProgress)
213                 {
214                         if (selection.contains(Extents()))
215                                 state = OSSelected;
216                         else
217                                 state = OSInactive;
218
219                         return;
220                 }
221
222                 // No need to do any checking if we're already selected...
223 //              if (state == OSSelected)
224 //                      return;
225
226 //              oldState = state;
227 //              needUpdate = true;
228 //              if (dragging &&
229                 bool oldHit = hit;
230                 hit = false;
231
232                 for(i=objects.begin(); i!=objects.end(); i++)
233                 {
234                         if ((*i)->HitTest(point))
235                         {
236 //                              state = OSSelected;
237 //                              return;
238                                 hit = true;
239                                 break;
240                         }
241                 }
242
243                 needUpdate = (oldHit != hit ? true : false);
244 //              state = oldState;
245
246                 if (dragging)
247                 {
248                         Vector delta = point - oldPoint;
249
250                         for(i=objects.begin(); i!=objects.end(); i++)
251                                 (*i)->Translate(delta);
252
253                         oldPoint = point;
254                         needUpdate = true;
255                 }
256
257                 return;
258         }
259
260         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
261         {
262 //              if (objects[i]->GetState() == OSSelected)
263                 (*i)->PointerMoved(point);
264         }
265
266         // Generic container doesn't need this???
267 //      needUpdate = false;
268 }
269
270
271 /*virtual*/ void Container::PointerReleased(void)
272 {
273         if (!isTopLevelContainer)
274         {
275                 dragging = false;
276                 return;
277         }
278 #if 0
279         dragging = false;
280         draggingHandle1 = false;
281         draggingHandle2 = false;
282
283         // Here we check for just a click: If object was clicked and dragged, then
284         // revert to the old state (OSInactive). Otherwise, keep the new state that
285         // we set.
286 /*
287 Maybe it would be better to just check for "object was dragged" state and not
288 have to worry about keeping track of old states...
289 */
290         if (objectWasDragged)
291                 state = oldState;
292 //Note that the preceeding is unnecessary for a generic container!
293 #endif
294
295 //      for(int i=0; i<(int)objects.size(); i++)
296 //              objects[i]->PointerReleased();
297         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
298                 (*i)->PointerReleased();
299 }
300
301
302 /*virtual*/ bool Container::NeedsUpdate(void)
303 {
304         // If this is *not* a top level container, then we treat it as an
305         // aggregate object.
306         if (!isTopLevelContainer)
307         {
308                 return needUpdate;
309         }
310
311         // Search through objects for one that needs an update; if one is found,
312         // return immediately.
313         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
314         {
315                 if ((*i)->NeedsUpdate())
316                         return true;
317         }
318
319         return false;
320 }
321
322
323 /*virtual*/ void Container::Add(Object * object)
324 {
325         objects.push_back(object);
326 printf("Container: Added object (=$%X). size = %li\n", object, objects.size());
327 }
328
329
330 /*virtual*/ QRectF Container::Extents(void)
331 {
332         QRectF bounds;
333
334         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
335                 bounds = bounds.united((*i)->Extents());
336
337         return bounds;
338 }
339
340
341 void Container::Delete(Object * objectToDelete)
342 {
343         std::vector<Object *>::iterator i = objects.begin();
344
345         while (i != objects.end())
346         {
347                 if (*i == objectToDelete)
348                 {
349                         objects.erase(i);
350                         delete objectToDelete;
351                         return;
352                 }
353
354                 i++;
355         }
356 }
357
358
359 void Container::DeleteSelectedItems(void)
360 {
361         std::vector<Object *>::iterator i = objects.begin();
362
363         while (i != objects.end())
364         {
365                 if ((*i)->state == OSSelected)
366                 {
367                         delete *i;
368                         objects.erase(i);
369                 }
370                 else
371                         i++;
372         }
373 }
374
375
376 void Container::Clear(void)
377 {
378         std::vector<Object *>::iterator i = objects.begin();
379
380         while (i != objects.end())
381         {
382 printf("Container: Deleting object ($%X)...\n", *i);
383                 delete (*i);
384                 objects.erase(i);
385         }
386 }
387
388
389 void Container::SelectAll(void)
390 {
391         for(unsigned int i=0; i<objects.size(); i++)
392                 objects[i]->state = OSSelected;
393 }
394
395
396 void Container::DeselectAll(void)
397 {
398         for(unsigned int i=0; i<objects.size(); i++)
399                 objects[i]->state = OSInactive;
400 }
401
402
403 int Container::ItemsSelected(void)
404 {
405         int selected = 0;
406
407         for(uint i=0; i<objects.size(); i++)
408                 if (objects[i]->state == OSSelected)
409                         selected++;
410
411         return selected;
412 }
413
414
415 Object * Container::SelectedItem(unsigned int index)
416 {
417         unsigned int selectedIndex = 0;
418
419         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
420         {
421                 if ((*i)->state == OSSelected)
422                 {
423                         if (selectedIndex == index)
424                                 return *i;
425                         else
426                                 selectedIndex++;
427                 }
428         }
429
430         return NULL;
431 }
432
433
434 void Container::MoveContentsTo(Container * newContainer)
435 {
436         // Sanity check
437         if (newContainer == NULL)
438                 return;
439
440         // Shuffle the contents of this container to the new one
441 //      for(unsigned int i=0; i<objects.size(); i++)
442         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
443         {
444                 newContainer->Add(*i);
445                 (*i)->Reparent(newContainer);
446         }
447
448         // & clear our vector
449         objects.clear();
450 }
451
452
453 void Container::MoveSelectedContentsTo(Container * newContainer)
454 {
455         // Sanity check
456         if (newContainer == NULL)
457                 return;
458
459         // Shuffle the contents of this container to the new one
460         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end();)
461         {
462                 if ((*i)->state != OSSelected)
463                 {
464                         i++;
465                         continue;
466                 }
467
468                 newContainer->Add(*i);
469                 (*i)->Reparent(newContainer);
470                 objects.erase(i);
471         }
472 }
473
474
475 void Container::ResizeAllDimensions(double newSize)
476 {
477         for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
478         {
479                 if ((*i)->type == OTDimension)
480                         ((Dimension *)(*i))->size = newSize;
481                 if ((*i)->type == OTContainer)
482                         ((Container *)(*i))->ResizeAllDimensions(newSize);
483         }
484 }
485
486
487 /*virtual*/ void Container::Enumerate(FILE * file)
488 {
489         // Only put "CONTAINER" markers if *not* the top level container
490 //      if (parent != NULL)
491         if (!isTopLevelContainer)
492                 fprintf(file, "CONTAINER\n");
493
494         for(uint i=0; i<objects.size(); i++)
495                 objects[i]->Enumerate(file);
496
497 //      if (parent != NULL)
498         if (!isTopLevelContainer)
499                 fprintf(file, "ENDCONTAINER\n");
500 }
501