]> Shamusworld >> Repos - architektonas/blobdiff - src/line.cpp
Trim tool now works for Lines, but inaccurate.
[architektonas] / src / line.cpp
index 60e5ab0c1095ff67856aa5b333bf72a0d46afbcf..46a6d85860c8d598b8f76497f9ea4ace005bb20d 100644 (file)
@@ -4,7 +4,7 @@
 // (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
 // ---  ----------  ------------------------------------------------------------
 #include "line.h"
 
 #include <QtGui>
+#include "container.h"
 #include "dimension.h"
+#include "geometry.h"
+#include "mathconstants.h"
+#include "painter.h"
 
-Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p), endpoint(p2),
+
+Line::Line(Vector p1, Vector p2, Object * p/*= NULL*/): Object(p1, p),
+       /*type(OTLine),*/ endpoint(p2),
        draggingLine(false), draggingHandle1(false), draggingHandle2(false), //needUpdate(false),
-       length(Vector::Magnitude(p2, p1)), hitPoint1(false), hitPoint2(false), hitLine(false)
+       length(Vector::Magnitude(p2, p1)), angle(Vector(endpoint - position).Unit()),
+       hitPoint1(false), hitPoint2(false), hitLine(false)
 {
+       type = OTLine;
 }
 
+
 Line::~Line()
 {
+// Taking care of connections should be done by the Container, as we don't know
+// anything about any other object connected to this one.
+#if 0
        // If there are any attached Dimensions, we must set the attachment points
        // to NULL since they will no longer be valid.
        if (attachedDimension)
@@ -39,20 +51,32 @@ Line::~Line()
        // IT WOULD BE NICE to have any object points attached to this line automagically
        // connect to this dimension object at this point, instead of just becoming
        // detached.
+#endif
+//actually not true, we know the object pointer and parameter!
+//actuall, the Object base class does this for us...!
+#if 0
+       std::vector<Connection>::iterator i;
+
+       for(i=connected.begin(); i!=connected.end(); i++)
+       {
+               (*i).object->Disconnect(this, (*i).t);
+       }
+#endif
 }
 
-/*virtual*/ void Line::Draw(QPainter * painter)
+
+/*virtual*/ void Line::Draw(Painter * painter)
 {
-       painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
+       painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
 
        if ((state == OSSelected) || ((state == OSInactive) && hitPoint1))
-               painter->drawEllipse(QPointF(position.x, position.y), 4.0, 4.0);
+               painter->DrawHandle(position);
 
        if ((state == OSSelected) || ((state == OSInactive) && hitPoint2))
-               painter->drawEllipse(QPointF(endpoint.x, endpoint.y), 4.0, 4.0);
+               painter->DrawHandle(endpoint);
 
        if ((state == OSInactive) && !hitLine)
-               painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
+               painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
 
        if (Object::fixedLength && (draggingHandle1 || draggingHandle2))
        {
@@ -62,16 +86,28 @@ Line::~Line()
                Vector current(point2 - point1);
                Vector v = current.Unit() * length;
                Vector v2 = point1 + v;
-               painter->drawLine((int)point1.x, (int)point1.y, (int)v2.x, (int)v2.y);
+               painter->DrawLine(point1, v2);
 
                if (current.Magnitude() > length)
                {
-                       painter->setPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
-                       painter->drawLine((int)v2.x, (int)v2.y, (int)point2.x, (int)point2.y);
+                       painter->SetPen(QPen(QColor(128, 0, 0), 1.0, Qt::DashLine));
+                       painter->DrawLine(v2, point2);
                }
        }
        else
-               painter->drawLine((int)position.x, (int)position.y, (int)endpoint.x, (int)endpoint.y);
+               painter->DrawLine(position, endpoint);
+
+       // If we're dragging an endpoint, draw an information panel showing both
+       // the length and angle being set.
+       if (draggingHandle1 || draggingHandle2)
+       {
+               double absAngle = (Vector(endpoint - position).Angle()) * RADIANS_TO_DEGREES;
+               double absLength = Vector(position - endpoint).Magnitude();
+
+               QString text = QObject::tr("Length: %1 in.\n") + QChar(0x2221) + QObject::tr(": %2");
+               text = text.arg(absLength).arg(absAngle);
+               painter->DrawInformativeText(text);
+       }
 }
 
 /*virtual*/ Vector Line::Center(void)
@@ -83,10 +119,35 @@ Line::~Line()
 
 /*virtual*/ bool Line::Collided(Vector point)
 {
-// Can't assume this!
-// Actually, we can, since this is a mouse down event here.
+/*
+what we can do here is set ignoreClicks to true to keep other objects that are
+selected from deselecting themselves. Will that fuck up something else? Not sure
+yet... :-/
+Actually, this is done here to keep tools from selecting stuff inadvertantly...
+*/
+       // We can assume this, since this is a mouse down event here.
        objectWasDragged = false;
-       HitTest(point);
+       bool hit = HitTest(point);
+
+       // Someone told us to fuck off, so we'll fuck off. :-)
+       if (ignoreClicks)
+//             return false;
+               return hit;
+
+       // Now that we've done our hit testing on the non-snapped point, snap it if
+       // necessary...
+       if (snapToGrid)
+               point = SnapPointToGrid(point);
+
+// this is shite. this should be checked for in the Container, not here!
+#warning "!!! This should be checked for in Container, not here !!!"
+       // If we're part of a non-top-level container, send this signal to it
+       if (parent->type == OTContainer && !((Container *)parent)->isTopLevelContainer
+               && (hitLine || hitPoint1 || hitPoint2))
+       {
+               parent->state = OSSelected;
+               return true;
+       }
 
 /*
 There's a small problem here with the implementation: You can have a dimension tied
@@ -97,7 +158,7 @@ We solve this by allowing only *one* Dimension object to be attached to the Line
 Arc, etc. and by giving the Dimension object a pointer to our endpoints.
 
 Problem still arises when we delete this object; The attached Dimension object will
-then have bad pointers! What is *should* do is delete the object if and only if this
+then have bad pointers! What it *should* do is delete the object if and only if this
 line is not attached to any other object. If it is, then one of those attachment
 points should be sent to the dimension object (done for position & endpoint).
 
@@ -109,6 +170,13 @@ key to make it draw/show on the other side...
 
 TODO: Make Dimension preview with modifier keys for showing on other side
 */
+/*
+
+N.B.: This no longer works, as the DrawDimension object takes precedence over this code.
+      THIS DOES NOTHING ANYMORE!!!
+
+*/
+#if 0
        // Is the dimension tool active? Let's use it:
        if (dimensionActive)
        {
@@ -116,6 +184,7 @@ TODO: Make Dimension preview with modifier keys for showing on other side
                // (Priorities are taken care of in HitTest()...)
                if (hitLine)
                {
+#if 0
                        if (attachedDimension == NULL)
                        {
                                // How to get this object into the top level container???
@@ -125,7 +194,7 @@ level container, it won't get drawn...
 But we can fix that by making this object call any attached object's (like
 a dimension only) Draw() function... :-/
 */
-                               attachedDimension = new Dimension(&position, &endpoint, this);
+                               attachedDimension = new Dimension(&position, &endpoint, DTLinear, this);
 
                                if (parent != NULL)
                                        parent->Add(attachedDimension);
@@ -135,33 +204,52 @@ a dimension only) Draw() function... :-/
                                // If there's one already there, tell it to flip sides...
                                attachedDimension->FlipSides();
                        }
+#else
+                       // New approach here: We look for connected objects.
+                       Object * attachedDimension = FindAttachedDimension();
+
+                       if (attachedDimension)
+                       {
+                               // If there's an attached Dimension, tell it to switch sides...
+                               ((Dimension *)attachedDimension)->FlipSides();
+                       }
+                       else
+                       {
+                               // Otherwise, we make a new one and attach it here.
+                               attachedDimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
+                               connected.push_back(Connection(attachedDimension, 0));
+                               connected.push_back(Connection(attachedDimension, 1.0));
+
+                               if (parent != NULL)
+                                       parent->Add(attachedDimension);
+                       }
+#endif
 
                        return true;
                }
        }
-
+#endif
 
        if (state == OSInactive)
        {
-//printf("Line: 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
-//I think what's needed is an Object class variable/method that can be changed by the TLC and
-//called in derived classes to properly scale the location to the current zoom level. That *should* work.
-
-// ALSO: Need to code a global (read: Object class) variable that tells use whether a modifier
-//       key was pressed in addition to the mouse click, so we can do stuff like, say, hold
-//       down CTRL and be able to do multiple selecting of objects (in that case, we would
-//       keep the Object state from changing).
+//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
+//I think what's needed is an Object class variable/method that can be changed
+//by the TLC and called in derived classes to properly scale the location to
+//the current zoom level. That *should* work.
+
+// ALSO: Need to code a global (read: Object class) variable that tells use
+//       whether a modifier key was pressed in addition to the mouse click, so
+//       we can do stuff like, say, hold down CTRL and be able to do multiple
+//       selecting of objects (in that case, we would keep the Object state
+//       from changing).
                if (hitPoint1)
                {
                        oldState = state;
                        state = OSSelected;
-                       oldPoint = position; //maybe "position"?
+                       oldPoint = position;
                        draggingHandle1 = true;
                        return true;
                }
@@ -169,7 +257,7 @@ a dimension only) Draw() function... :-/
                {
                        oldState = state;
                        state = OSSelected;
-                       oldPoint = endpoint; //maybe "position"?
+                       oldPoint = endpoint;
                        draggingHandle2 = true;
                        return true;
                }
@@ -184,33 +272,52 @@ a dimension only) Draw() function... :-/
        }
        else if (state == OSSelected)
        {
-               // Here we test for collision with handles as well! (SOON!) [I think it works...NOPE]
-/*
-Like so:
-               if (v1.Magnitude() < 2.0) // Handle #1
-               else if (v2.Magnitude() < 2.0) // Handle #2
-*/
                if (hitLine)
                {
                        oldState = state;
 //                     state = OSInactive;
                        oldPoint = point;
                        draggingLine = true;
+
+                       // Toggle selected state if CTRL held
+                       if (qApp->keyboardModifiers() == Qt::ControlModifier)
+                               state = OSInactive;
+
                        return true;
                }
        }
 
+       // If CTRL is held, then we bypass the "turn off" code. Still didn't hit
+       // *this* object though. :-)
+       if (qApp->keyboardModifiers() == Qt::ControlModifier)
+               return false;
+
        // If we got here, we clicked on nothing, so set the object to inactive.
        // (Once we can read key modifiers, we can override this to allow multiple selection.)
        state = OSInactive;
        return false;
 }
 
-/*virtual*/ void Line::PointerMoved(Vector point)
+
+/*virtual*/ bool Line::PointerMoved(Vector point)
 {
-       // Hit test tells us what we hit (if anything) through boolean variables. It
-       // also tells us whether or not the state changed.
-       needUpdate = HitTest(point);
+       if (selectionInProgress)
+       {
+               // Check for whether or not the rect contains this line
+               if (selection.contains(position.x, position.y)
+                       && selection.contains(endpoint.x, endpoint.y))
+                       state = OSSelected;
+               else
+                       state = OSInactive;
+
+               return false;
+       }
+
+       // Hit test tells us what we hit (if anything) through boolean variables. (It
+       // also tells us whether or not the state changed. --not any more)
+       SaveHitState();
+       bool hovered = HitTest(point);
+       needUpdate = HitStateChanged();
 
        objectWasDragged = (draggingLine | draggingHandle1 | draggingHandle2);
 
@@ -226,6 +333,22 @@ Like so:
 
                oldPoint = point;
                needUpdate = true;
+
+//doesn't work         QMainWindow::statusBar()->setText("You are manipulating a line");
+
+               // Tell connected objects to move themselves...
+               if (draggingLine)
+               {
+                       std::vector<Connection>::iterator i;
+
+                       for(i=connected.begin(); i!=connected.end(); i++)
+                       {
+                               if ((*i).object->type == OTLine)
+                                       ((Line *)((*i).object))->MovePointAtParameter((*i).t, delta);
+                               else if ((*i).object->type == OTDimension)
+                                       ((Dimension *)((*i).object))->MovePointAtParameter((*i).t, delta);
+                       }
+               }
        }
 
 /*
@@ -238,18 +361,20 @@ Ugly ways to do it:
 
 More elegant ways:
  - Pass the point in a notification function (how?)
- - Pass the point as a reference to the class instance object (&endpoint). This way, the line
-   doesn't have to care about keeping track of Dimensions connected to it. But still have to
-   care about other connected entities (other Lines, Circles, Arcs, Splines, Texts, etc). I
-   think I'd be OK with this.
-   Since the Dimension has a pointer to our object, all we have to do is update our coordinates
-   and the Dimension object will adjust itself on the next repaint. Problem solved, and we don't
-   have to know anything about how many Dimensions are connected to us, or where! \o/
+ - Pass the point as a reference to the class instance object (&endpoint). This
+   way, the line doesn't have to care about keeping track of Dimensions
+   connected to it. But still have to care about other connected entities
+   (other Lines, Circles, Arcs, Splines, Texts, etc). I think I'd be OK with
+   this. Since the Dimension has a pointer to our object, all we have to do is
+   update our coordinates and the Dimension object will adjust itself on the
+   next repaint. Problem solved, and we don't have to know anything about how
+   many Dimensions are connected to us, or where! \o/
    The question then becomes, how do we do this kind of coupling???
 
-We need to know about connected entities so that we can have them either move in expected ways
-or constrain the movement of this Line object. This is how we will be a cut above all other CAD
-software currently out there: the GUI will try to do the right thing, most of the time. :-)
+We need to know about connected entities so that we can have them either move
+in expected ways or constrain the movement of this Line object. This is how we
+will be a cut above all other CAD software currently out there: the GUI will
+try to do the right thing, most of the time. :-)
 */
        if (needUpdate)
        {
@@ -257,13 +382,24 @@ software currently out there: the GUI will try to do the right thing, most of th
                Vector point1 = (draggingHandle1 ? endpoint : position);
                Vector point2 = (draggingHandle1 ? position : endpoint);
 
-               Vector current(point2, point1);
-               Vector v = current.Unit() * length;
-               Vector v2 = point1 + v;
+               if (Object::fixedAngle)
+               {
+                       // Here we calculate the component of the current vector along the fixed angle.
+                       // A_compB = (A . Bu) * Bu
+                       double magnitudeAlongB = Vector::Dot(Vector(point2 - point1), angle);
+/*
+Actually, this isn't quite right. What we want to do is look for the intersection along either
+the horizontal line or vertical line that intersects from the current mouse position.
+*/
 
-               //bleh
-               if (!Object::fixedLength)
-                       v2 = point2;
+                       if (draggingHandle1)
+                               position = endpoint + (angle * magnitudeAlongB);
+
+                       if (draggingHandle2)
+                               endpoint = position + (angle * magnitudeAlongB);
+               }
+//             else
+//                     v2 = point2;
 
 //If we tell the dimension to flip sides, this is no longer a valid
 //assumption. !!! FIX !!!
@@ -280,8 +416,11 @@ software currently out there: the GUI will try to do the right thing, most of th
                        dimPoint2->SetPoint2(draggingHandle2 ? v2 : endpoint);
 #endif
        }
+
+       return hovered;
 }
 
+
 /*virtual*/ void Line::PointerReleased(void)
 {
        if (draggingHandle1 || draggingHandle2)
@@ -296,98 +435,44 @@ software currently out there: the GUI will try to do the right thing, most of th
                        }
                        else                                    // endpoint
                        {
-//                             Vector v1 = endpoint - position;
                                Vector v = Vector(endpoint - position).Unit() * length;
                                endpoint = position + v;
                        }
                }
                else
                {
-                       // Otherwise, we calculate the new length, just in case on the next move
-                       // it turns out to have a fixed length. :-)
+                       // Otherwise, we calculate the new length, just in case on the next
+                       // move it turns out to have a fixed length. :-)
                        length = Vector(endpoint - position).Magnitude();
                }
+
+               if (!Object::fixedAngle)
+               {
+                       // Calculate the new angle, just in case on the next move it turns
+                       // out to be fixed. :-)
+                       angle = Vector(endpoint - position).Unit();
+               }
        }
 
        draggingLine = false;
        draggingHandle1 = false;
        draggingHandle2 = false;
 
-//     hitPoint1 = hitPoint2 = hitLine = 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...
-*/
        if (objectWasDragged)
                state = oldState;
 }
 
-#if 0
-void Line::SetDimensionOnPoint1(Dimension * dimension)
-{
-       dimPoint1 = dimension;
-
-       if (dimension)
-               dimension->SetPoint1(position);
-}
-
-void Line::SetDimensionOnPoint2(Dimension * dimension)
-{
-       dimPoint2 = dimension;
-
-       if (dimension)
-               dimension->SetPoint2(endpoint);
-}
-#else
-void Line::SetDimensionOnLine(Dimension * dimension/*=NULL*/)
-{
-       // If they don't pass one in, create it for the caller.
-       if (dimension == NULL)
-       {
-               dimension = new Dimension(&position, &endpoint, this);
-
-               if (parent)
-                       parent->Add(dimension);
-       }
-
-       attachedDimension = dimension;
-
-       // After we set the points here, we don't have to care about them anymore.
-       if (dimension)
-       {
-               dimension->SetPoint1(&position);
-               dimension->SetPoint2(&endpoint);
-       }
-}
-#endif
 
-bool Line::HitTest(Point point)
+/*virtual*/ bool Line::HitTest(Point point)
 {
-       SaveState();
-
        hitPoint1 = hitPoint2 = hitLine = false;
        Vector lineSegment = endpoint - position;
        Vector v1 = point - position;
        Vector v2 = point - endpoint;
-       double parameterizedPoint = lineSegment.Dot(v1) / lineSegment.Magnitude(), distance;
-
-       // Geometric interpretation:
-       // The parameterized point on the vector lineSegment is where the perpendicular
-       // intersects lineSegment. If pp < 0, then the perpendicular lies beyond the 1st
-       // endpoint. If pp > length of ls, then the perpendicular lies beyond the 2nd endpoint.
-
-       if (parameterizedPoint < 0.0)
-               distance = v1.Magnitude();
-       else if (parameterizedPoint > lineSegment.Magnitude())
-               distance = v2.Magnitude();
-       else
-               // distance = ?Det?(ls, v1) / |ls|
-               distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y) / lineSegment.Magnitude());
+       double t = Geometry::ParameterOfLineAndPoint(position, endpoint, point);
+       double distance;
 
-       // Geometric interpretation of the above:
+       // Geometric interpretation of "distance = ?Det?(ls, v1) / |ls|":
        // If the segment endpoints are s and e, and the point is p, then the test
        // for the perpendicular intercepting the segment is equivalent to insisting
        // that the two dot products {s-e}.{s-p} and {e-s}.{e-p} are both non-negative.
@@ -405,26 +490,207 @@ bool Line::HitTest(Point point)
        // all other rows, we end up with the matrix on the right which greatly
        // simplifies the calculation of the determinant.
 
-//How do we determine distance here? Especially if zoomed in or out???
-#warning "!!! Distances tested for may not be valid if zoomed in or out !!!"
-       if (v1.Magnitude() < 8.0)
+       if (t < 0.0)
+               distance = v1.Magnitude();
+       else if (t > 1.0)
+               distance = v2.Magnitude();
+       else
+               // distance = ?Det?(ls, v1) / |ls|
+               distance = fabs((lineSegment.x * v1.y - v1.x * lineSegment.y)
+                       / lineSegment.Magnitude());
+
+       if ((v1.Magnitude() * Painter::zoom) < 8.0)
                hitPoint1 = true;
-       else if (v2.Magnitude() < 8.0)
+       else if ((v2.Magnitude() * Painter::zoom) < 8.0)
                hitPoint2 = true;
-       else if (distance < 5.0)
+       else if ((distance * Painter::zoom) < 5.0)
                hitLine = true;
 
-       return StateChanged();
+       return (hitPoint1 || hitPoint2 || hitLine ? true : false);
+}
+
+
+// Check to see if the point passed in coincides with any we have. If so, return a
+// pointer to it; otherwise, return NULL.
+/*virtual*/ Vector * Line::GetPointAt(Vector v)
+{
+       if (v == position)
+               return &position;
+       else if (v == endpoint)
+               return &endpoint;
+
+       return 0;
+}
+
+
+/*virtual*/ void Line::Enumerate(FILE * file)
+{
+       fprintf(file, "LINE %i (%lf,%lf) (%lf,%lf)\n", layer, position.x, position.y, endpoint.x, endpoint.y);
+}
+
+
+/*virtual*/ Object * Line::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.
+*/
+       return new Line(position, endpoint, parent);
 }
 
-void Line::SaveState(void)
+
+/*virtual*/ Vector Line::GetPointAtParameter(double parameter)
+{
+// Is there any real reason to clamp this to the endpoints?
+// (hey, whaddya know? this was masking a bug!)
+#if 0
+       if (parameter <= 0)
+               return position;
+       else if (parameter >= 1.0)
+               return endpoint;
+#endif
+
+       // The parameter is a percentage of the length of the vector, so all we
+       // have to do is scale the vector by it to find the point.
+       return position + (Vector(position, endpoint) * parameter);
+}
+
+
+/*virtual*/ void Line::MovePointAtParameter(double parameter, Vector v)
+{
+       if (parameter == 0)
+               position += v;
+       else if (parameter == 1.0)
+               endpoint += v;
+       else
+               {} // Not sure how to handle this case :-P
+}
+
+
+/*virtual*/ QRectF Line::Extents(void)
+{
+       QRectF rect(QPointF(position.x, position.y), QPointF(endpoint.x, endpoint.y));
+       return rect.normalized();
+}
+
+
+/*virtual*/ void Line::Translate(Vector amount)
+{
+       position += amount;
+       endpoint += amount;
+}
+
+
+/*virtual*/ void Line::Rotate(Point point, double angle)
+{
+       Point l1 = Geometry::RotatePointAroundPoint(position, point, angle);
+       Point l2 = Geometry::RotatePointAroundPoint(endpoint, point, angle);
+       position = l1;
+       endpoint = l2;
+}
+
+
+/*virtual*/ void Line::Scale(Point point, double amount)
+{
+}
+
+
+/*virtual*/ void Line::Mirror(Point p1, Point p2)
+{
+       Point l1 = Geometry::MirrorPointAroundLine(position, p1, p2);
+       Point l2 = Geometry::MirrorPointAroundLine(endpoint, p1, p2);
+       position = l1;
+       endpoint = l2;
+}
+
+
+/*virtual*/ void Line::Save(void)
+{
+       Object::Save();
+       oldEndpoint = endpoint;
+}
+
+
+/*virtual*/ void Line::Restore(void)
+{
+       Object::Restore();
+       endpoint = oldEndpoint;
+}
+
+
+void Line::SetDimensionOnLine(Dimension * dimension/*= NULL*/)
+{
+       // If they don't pass one in, create it for the caller.
+       // But ONLY if this line has a parent container!
+       // This is really bad to do here, it should be done in the parent container, always!
+#warning "!!! Parent container should be creating Dimension object !!!"
+       if ((dimension == NULL) && (parent != NULL))
+       {
+//printf("Line::SetDimensionOnLine(): Creating new dimension...\n");
+               dimension = new Dimension(position, endpoint, DTLinear, parent);
+//             dimension = new Dimension(Connection(this, 0), Connection(this, 1.0), DTLinear, this);
+
+               // THIS IS SERIOUS!!! WITHOUT A PARENT, THIS OBJECT IS IN LIMBO!!!
+//             if (parent)
+//{
+//printf("Line::SetDimensionOnLine(): Adding to parent...\n");
+               parent->Add(dimension);
+//}
+       }
+
+       dimension->Connect(this, 0);
+       dimension->Connect(this, 1.0);
+
+       // Make sure the Dimension is connected to us...
+       Connect(dimension, 0);
+       Connect(dimension, 1.0);
+
+       dimension->position = position;
+       dimension->endpoint = endpoint;
+}
+
+
+Object * Line::FindAttachedDimension(void)
+{
+       // Is there anything connected to this line? If not, return NULL
+       if (connected.size() < 2)
+               return NULL;
+
+       // Otherwise, we have to search our objects to see if there's a likely
+       // candidate. In this case, we're looking for a pointer to the same object
+       // with a parameter of 0 and 1 respectively. This is O((n^2)/2).
+       for(uint i=0; i<connected.size(); i++)
+       {
+               for(uint j=i+1; j<connected.size(); j++)
+               {
+//printf("Line: connected[i]=%X, connected[j]=%X, connected[i].t=%lf, connected[j].t=%lf\n", connected[i].object, connected[j].object, connected[i].t, connected[j].t);
+                       if ((connected[i].object == connected[j].object)
+                               && ((connected[i].t == 0 && connected[j].t == 1.0)
+                               || (connected[i].t == 1.0 && connected[j].t == 0)))
+                               return connected[i].object;
+               }
+       }
+
+       // Didn't find anything, so return NULL
+       return NULL;
+}
+
+
+void Line::SaveHitState(void)
 {
        oldHitPoint1 = hitPoint1;
        oldHitPoint2 = hitPoint2;
        oldHitLine = hitLine;
 }
 
-bool Line::StateChanged(void)
+
+bool Line::HitStateChanged(void)
 {
        if ((hitPoint1 != oldHitPoint1) || (hitPoint2 != oldHitPoint2) || (hitLine != oldHitLine))
                return true;
@@ -432,6 +698,7 @@ bool Line::StateChanged(void)
        return false;
 }
 
+
 /*
 Intersection of two lines:
 
@@ -472,7 +739,7 @@ rearranging we get:
 t(d1x) - s(d2x) = p2x - p0x
 t(d1y) - s(d2y) = p2y - p0y
 
-Determinant D is ad - bc where the matrix look like:
+Determinant D is ad - bc where the matrix looks like:
 
 a b
 c d