+//
// container.cpp: Container object
//
// Part of the Architektonas Project
// (C) 2011 Underground Software
// See the README and GPLv3 files for licensing and warranty information
//
-// JLH = James L. Hammons <jlhamm@acm.org>
+// JLH = James Hammons <jlhamm@acm.org>
//
// WHO WHEN WHAT
// --- ---------- ------------------------------------------------------------
// JLH 03/30/2011 Created this file
+// JLH 06/02/2011 Added code to delete objects in this container when they go
+// out of scope
//
#include "container.h"
#include <QtGui>
-
+#include "dimension.h"
+#include "painter.h"
Container::Container(Vector p1, Object * p/*= NULL*/): Object(p1, p),
- dragging(false), draggingHandle1(false), draggingHandle2(false)//, needUpdate(false)
+ isTopLevelContainer(false),
+ dragging(false), draggingHandle1(false), draggingHandle2(false),
+ hit(false)//, needUpdate(false)
{
+ type = OTContainer;
+ state = OSInactive;
+}
+
+// Copy constructor
+Container::Container(const Container & copy): Object(copy.position, copy.parent)
+{
+ // Use overloaded assignment operator
+ *this = copy;
+ type = OTContainer;
+ state = OSInactive;
}
Container::~Container()
{
+ Clear();
}
-/*virtual*/ void Container::Draw(QPainter * painter)
+// Assignment operator
+Container & Container::operator=(const Container & from)
{
- for(int i=0; i<(int)objects.size(); i++)
- objects[i]->Draw(painter);
+ // Take care of self-assignment
+ if (this == &from)
+ return *this;
+
+ Clear();
+ std::vector<Object *>::const_iterator i;
+
+ for(i=from.objects.begin(); i!=from.objects.end(); i++)
+ {
+printf("Container: Copying object $%08X...\n", *i);
+ Object * object = (*i)->Copy();
+ objects.push_back(object);
+ }
+
+ return *this;
+}
+
+/*virtual*/ void Container::Draw(Painter * painter)
+{
+ QRectF boundary;
+
+//int a=1;
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+//printf("Containter::Draw item #%i [%X]...\n", a++, *i);
+ (*i)->Draw(painter);
+ boundary = boundary.united((*i)->Extents());
+ }
+
+ if ((state == OSSelected) || hit)
+ {
+ if (hit)
+ painter->SetPen(QPen(Qt::magenta, 1.0, Qt::DashLine));
+ else
+ painter->SetPen(QPen(Qt::blue, 2.0, Qt::DashLine));
+
+ painter->SetBrush(QBrush(Qt::NoBrush));
+ painter->DrawPaddedRect(boundary);
+ }
}
/*virtual*/ Vector Container::Center(void)
Also: should put the snap logic into the Object base class (as a static method)...
*/
+// Need to add checking here for clicking on a member of a group (Container),
+// and checking for if it's a top level container (the DrawingView's document).
+/*
+One approach is to check for the parent of the container: If it's NULL, then it's
+the DrawingView's document. It might be better, though, to set a boolean like
+isTopLevelContainer so that we can do things like edit members of a group without
+having to ungroup them first (like Inkscape).
+*/
/*virtual*/ bool Container::Collided(Vector point)
{
objectWasDragged = false;
- Vector v1 = position - point;
+ bool collision = false;
+ lastObjectClicked = NULL;
-#if 0
- if (state == OSInactive)
- {
-//printf("Circle: pp = %lf, length = %lf, distance = %lf\n", parameterizedPoint, lineSegment.Magnitude(), distance);
-//printf(" v1.Magnitude = %lf, v2.Magnitude = %lf\n", v1.Magnitude(), v2.Magnitude());
-//printf(" point = %lf,%lf,%lf; p1 = %lf,%lf,%lf; p2 = %lf,%lf,%lf\n", point.x, point.y, point.z, position.x, position.y, position.z, endpoint.x, endpoint.y, endpoint.z);
-//printf(" \n", );
-//How to translate this into pixels from Document space???
-//Maybe we need to pass a scaling factor in here from the caller? That would make sense, as
-//the caller knows about the zoom factor and all that good kinda crap
- if (v1.Magnitude() < 10.0)
- {
- oldState = state;
- state = OSSelected;
- oldPoint = position; //maybe "position"?
- draggingHandle1 = true;
- return true;
- }
- else if ((v1.Magnitude() < radius + 2.0) && (v1.Magnitude() > radius - 2.0))
- {
- oldState = state;
- state = OSSelected;
- oldPoint = point;
- dragging = true;
- return true;
- }
- }
- else if (state == OSSelected)
+ // NOTE that this deletes the object on mouse down instead of mouse up.
+ // Have to check to see how it feels to do it that way...
+ // N.B.: This only works because the toolAction is not set, &
+ // Object::ignoreClicks isn't set either...
+ if (deleteActive)
{
- // Here we test for collision with handles as well! (SOON!)
-/*
-Like so:
- if (v1.Magnitude() < 2.0) // Handle #1
- else if (v2.Magnitude() < 2.0) // Handle #2
-*/
- if ((v1.Magnitude() < radius + 2.0) && (v1.Magnitude() > radius - 2.0))
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end();)
{
- oldState = state;
-// state = OSInactive;
- oldPoint = point;
- dragging = true;
- return true;
+ if ((*i)->Collided(point))
+ {
+printf("Container::Collided: Deleting object ($%X)\n", *i);
+ Object * objectToDelete = *i;
+ objects.erase(i);
+ delete objectToDelete;
+ }
+ else
+ i++;
}
}
-#else
- bool collision = false;
-
- // NOTE that this deletes the object on mouse down instead of mouse up. Have to
- // check to see how it feels to do it that way...
- if (deleteActive)
+ else
{
- for(int i=0; i<(int)objects.size(); i++)
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
{
- if (objects[i]->Collided(point))
+ if ((*i)->Collided(point))
{
- objects.erase(objects.begin() + i); // Calls the destructor, (deletes the object, I presume... O_o)
- break;
+ collision = true;
+ lastObjectClicked = *i;
+//printf("Container::Collided: lastObjectClicked = %X\n", lastObjectClicked);
}
}
}
+
+ if (snapToGrid)
+ point = SnapPointToGrid(point);
+
+ // We check to see if the container we're trying to access is the
+ // DrawingView's document. If so, we ignore the state of the container.
+ // Otherwise, we care about the state of the container. :-)
+ if (isTopLevelContainer)
+ state = OSInactive;
else
{
- for(int i=0; i<(int)objects.size(); i++)
+ state = (collision ? OSSelected : OSInactive);
+
+ if (state == OSSelected)
{
- if (objects[i]->Collided(point))
- collision = true;
+ DeselectAll();
+ dragging = true;
+ oldPoint = point;
}
}
-#endif
- // Do we decouple the state of the generic container from the objects inside??? Mebbe.
- state = OSInactive;
-// return false;
return collision;
}
+/*
+What we need to do is check for whether or not we're a top level container,
+and override the passing of stuff into the objects held. So here, if we're *NOT*
+a top level container, instead of passing PointerMoved to our contained objects,
+we check to see if our bounds are met (for selection rectangle, e.g.).
+
+Also, for things like being able to split point's hot spots we need to be able
+to check for that crap in the top level container. Which means that objects can
+still know how to move themselves, but they can also defer to their container
+as well. Which also means that things like HitTest() need to be in the Object
+class so that we can leverage that stuff here as well.
+*/
+
// The TLC is passing all mouse movement here, so we're doing the same here.
// Need to adjust all other objects to handle things correctly.
+
// One optimization that will need to be done eventually is to subdivide the screen
// into parts and keep subdividing until an acceptable number of objects lie within
// the slice. This way, the GUI will still be responsive and *not* have to test
// every object for collision.
-/*virtual*/ void Container::PointerMoved(Vector point)
+/*virtual*/ bool Container::PointerMoved(Vector point)
{
-// objectWasDragged = true;
-//printf("CONTAINER: PointerMoved()\n");
+ std::vector<Object *>::iterator i;
+ lastObjectHovered = penultimateObjectHovered = NULL;
+
+ if (!isTopLevelContainer)
+ {
+ // check for selection rectangle too
+ if (selectionInProgress)
+ {
+ if (selection.contains(Extents()))
+ state = OSSelected;
+ else
+ state = OSInactive;
+
+ return false;
+ }
+
+ // No need to do any checking if we're already selected...
+// if (state == OSSelected)
+// return;
+
+// oldState = state;
+// needUpdate = true;
+// if (dragging &&
+ bool oldHit = hit;
+ hit = false;
+
+ for(i=objects.begin(); i!=objects.end(); i++)
+ {
+ if ((*i)->HitTest(point))
+ {
+// state = OSSelected;
+// return;
+ hit = true;
+ break;
+ }
+ }
+
+ needUpdate = (oldHit != hit ? true : false);
+// state = oldState;
+
+ if (dragging)
+ {
+ Vector delta = point - oldPoint;
+
+ for(i=objects.begin(); i!=objects.end(); i++)
+ (*i)->Translate(delta);
- for(int i=0; i<(int)objects.size(); i++)
+ oldPoint = point;
+ needUpdate = true;
+ }
+
+ return false;
+ }
+
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
{
// if (objects[i]->GetState() == OSSelected)
- objects[i]->PointerMoved(point);
+ if ((*i)->PointerMoved(point))
+ {
+ penultimateObjectHovered = lastObjectHovered;
+ lastObjectHovered = *i;
+ }
}
// Generic container doesn't need this???
// needUpdate = false;
+ return (lastObjectHovered == NULL ? false : true);
}
/*virtual*/ void Container::PointerReleased(void)
{
+ if (!isTopLevelContainer)
+ {
+ dragging = false;
+ return;
+ }
+#if 0
dragging = false;
draggingHandle1 = false;
draggingHandle2 = false;
// Here we check for just a click: If object was clicked and dragged, then
// revert to the old state (OSInactive). Otherwise, keep the new state that
// we set.
-/*Maybe it would be better to just check for "object was dragged" state and not have to worry
-about keeping track of old states...
+/*
+Maybe it would be better to just check for "object was dragged" state and not
+have to worry about keeping track of old states...
*/
if (objectWasDragged)
state = oldState;
//Note that the preceeding is unnecessary for a generic container!
+#endif
- for(int i=0; i<(int)objects.size(); i++)
- objects[i]->PointerReleased();
+// for(int i=0; i<(int)objects.size(); i++)
+// objects[i]->PointerReleased();
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ (*i)->PointerReleased();
}
/*virtual*/ bool Container::NeedsUpdate(void)
{
- needUpdate = false;
+ // If this is *not* a top level container, then we treat it as an
+ // aggregate object.
+ if (!isTopLevelContainer)
+ {
+ return needUpdate;
+ }
- for(int i=0; i<(int)objects.size(); i++)
+ // Search through objects for one that needs an update; if one is found,
+ // return immediately.
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
{
- if (objects[i]->NeedsUpdate())
- needUpdate = true;
+ if ((*i)->NeedsUpdate())
+ return true;
}
- return needUpdate;
+ return false;
}
/*virtual*/ void Container::Add(Object * object)
{
objects.push_back(object);
+printf("Container: Added object (=$%X). size = %li\n", object, objects.size());
+}
+
+/*virtual*/ QRectF Container::Extents(void)
+{
+ QRectF bounds;
+
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ bounds = bounds.united((*i)->Extents());
+
+ return bounds;
+}
+
+void Container::Delete(Object * objectToDelete)
+{
+ std::vector<Object *>::iterator i = objects.begin();
+
+ while (i != objects.end())
+ {
+ if (*i == objectToDelete)
+ {
+ objects.erase(i);
+ delete objectToDelete;
+ return;
+ }
+
+ i++;
+ }
+}
+
+void Container::DeleteSelectedItems(void)
+{
+ std::vector<Object *>::iterator i = objects.begin();
+
+ while (i != objects.end())
+ {
+ if ((*i)->state == OSSelected)
+ {
+ delete *i;
+ objects.erase(i);
+ }
+ else
+ i++;
+ }
+}
+
+void Container::Clear(void)
+{
+ std::vector<Object *>::iterator i = objects.begin();
+
+ while (i != objects.end())
+ {
+printf("Container: Deleting object ($%X)...\n", *i);
+ delete (*i);
+ objects.erase(i);
+ }
+}
+
+void Container::SelectAll(void)
+{
+ for(unsigned int i=0; i<objects.size(); i++)
+ objects[i]->state = OSSelected;
+}
+
+void Container::DeselectAll(void)
+{
+ for(unsigned int i=0; i<objects.size(); i++)
+ objects[i]->state = OSInactive;
+}
+
+int Container::ItemsSelected(void)
+{
+ int selected = 0;
+
+ for(uint i=0; i<objects.size(); i++)
+ if (objects[i]->state == OSSelected)
+ selected++;
+
+ return selected;
+}
+
+Object * Container::SelectedItem(unsigned int index)
+{
+ unsigned int selectedIndex = 0;
+
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+ if ((*i)->state == OSSelected)
+ {
+ if (selectedIndex == index)
+ return *i;
+ else
+ selectedIndex++;
+ }
+ }
+
+ return NULL;
+}
+
+void Container::MoveContentsTo(Container * newContainer)
+{
+ // Sanity check
+ if (newContainer == NULL)
+ return;
+
+ // Shuffle the contents of this container to the new one
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+ newContainer->Add(*i);
+ (*i)->Reparent(newContainer);
+ }
+
+ // & clear our vector
+ objects.clear();
+}
+
+void Container::CopyContentsTo(Container * newContainer)
+{
+ // Sanity check
+ if (newContainer == NULL)
+ return;
+
+ // Shuffle the contents of this container to the new one
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ newContainer->Add((*i)->Copy());
+}
+
+void Container::MoveSelectedContentsTo(Container * newContainer)
+{
+ // Sanity check
+ if (newContainer == NULL)
+ return;
+
+ // Shuffle the contents of this container to the new one
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end();)
+ {
+ if ((*i)->state != OSSelected)
+ {
+ i++;
+ continue;
+ }
+
+ newContainer->Add(*i);
+ (*i)->Reparent(newContainer);
+ objects.erase(i);
+ }
+}
+
+void Container::CopySelectedContentsTo(Container * newContainer)
+{
+ // Sanity check
+ if (newContainer == NULL)
+ return;
+
+ // Copy the contents of this container to the new one
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+ if ((*i)->state == OSSelected)
+ newContainer->Add((*i)->Copy());
+ }
+}
+
+void Container::ResizeAllDimensions(double newSize)
+{
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+ if ((*i)->type == OTDimension)
+ ((Dimension *)(*i))->size = newSize;
+ if ((*i)->type == OTContainer)
+ ((Container *)(*i))->ResizeAllDimensions(newSize);
+ }
+}
+
+/*virtual*/ void Container::Enumerate(FILE * file)
+{
+ // Only put "CONTAINER" markers if *not* the top level container
+// if (parent != NULL)
+ if (!isTopLevelContainer)
+ fprintf(file, "CONTAINER %i\n", layer);
+
+ for(uint i=0; i<objects.size(); i++)
+ objects[i]->Enumerate(file);
+
+// if (parent != NULL)
+ if (!isTopLevelContainer)
+ fprintf(file, "ENDCONTAINER\n");
+}
+
+/*virtual*/ Object * Container::Copy(void)
+{
+#warning "!!! This doesn't take care of attached Dimensions !!!"
+/*
+This is a real problem. While having a pointer in the Dimension to this line's points
+is fast & easy, it creates a huge problem when trying to replicate an object like this.
+
+Maybe a way to fix that then, is to have reference numbers instead of pointers. That
+way, if you copy them, ... you might still have problems. Because you can't be sure if
+a copy will be persistant or not, you then *definitely* do not want them to have the
+same reference number.
+*/
+ Container * c = new Container(position, parent);
+ *c = *this;
+ return c;
+}
+
+/*virtual*/ void Container::Rotate(Point point, double angle)
+{
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ (*i)->Rotate(point, angle);
+}
+
+/*virtual*/ void Container::RotateSelected(Point point, double angle)
+{
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+ if ((*i)->state == OSSelected)
+ (*i)->Rotate(point, angle);
+ }
+}
+
+/*virtual*/ void Container::Mirror(Point p1, Point p2)
+{
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ (*i)->Mirror(p1, p2);
+}
+
+/*virtual*/ void Container::MirrorSelected(Point p1, Point p2)
+{
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ {
+ if ((*i)->state == OSSelected)
+ (*i)->Mirror(p1, p2);
+ }
+}
+
+/*virtual*/ void Container::Save(void)
+{
+ Object::Save();
+
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ (*i)->Save();
+}
+
+/*virtual*/ void Container::Restore(void)
+{
+ Object::Restore();
+
+ for(std::vector<Object *>::iterator i=objects.begin(); i!=objects.end(); i++)
+ (*i)->Restore();
}