From: Shamus Hammons Date: Sun, 11 Aug 2013 01:49:20 +0000 (-0500) Subject: Added ability to translate groups with Lines. X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?p=architektonas;a=commitdiff_plain;h=70297ac8ec7453e4196f4b58056bcfe4b04f2aca Added ability to translate groups with Lines. --- diff --git a/TODO b/TODO new file mode 100644 index 0000000..540d541 --- /dev/null +++ b/TODO @@ -0,0 +1,23 @@ +Stuff To Be Implemented/Fixed +----------------------------- + + - Add Arc + - Add Polygon + - Manipulate Dimension + - Object connections + - Group selection (kind done, needs more work though) + - Take movement code out of Objects and put it into top level Container + - Fix snap to grid to honor both states (right now, it's a weird mix of states) + - Add OSD routines so they don't have to be implemented in Objects + - Add OSD to Object creation + - Add layers + - Add blocks + - Add page layout + - Add pen color/style/width to Objects + - Add fill/hatch to Objects + - Fix zooming to be more intuitive + - Add other Dimension types, like radial, diametric, leader + - Add Ellipse + - Add Spline + - Add Text + diff --git a/res/architektonas.qrc b/res/architektonas.qrc index 8e79c6d..16d12d0 100644 --- a/res/architektonas.qrc +++ b/res/architektonas.qrc @@ -22,5 +22,7 @@ eye-closed.png lock-open.png lock-closed.png + connect-tool.png + disconnect-tool.png diff --git a/res/connect-tool.png b/res/connect-tool.png new file mode 100644 index 0000000..daac138 Binary files /dev/null and b/res/connect-tool.png differ diff --git a/res/disconnect-tool.png b/res/disconnect-tool.png new file mode 100644 index 0000000..e7ae516 Binary files /dev/null and b/res/disconnect-tool.png differ diff --git a/src/applicationwindow.cpp b/src/applicationwindow.cpp index bf181a8..daae37c 100644 --- a/src/applicationwindow.cpp +++ b/src/applicationwindow.cpp @@ -578,6 +578,10 @@ void ApplicationWindow::CreateActions(void) groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g")); connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping())); + connectAct = CreateAction(tr("&Connect"), tr("Connect"), tr("Connect objects at point."), QIcon(":/res/connect-tool.png"), QKeySequence("c,c")); + + disconnectAct = CreateAction(tr("&Disconnect"), tr("Disconnect"), tr("Disconnect objects joined at point."), QIcon(":/res/disconnect-tool.png"), QKeySequence("d,d")); + //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing... // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick. /* QActionGroup * group = new QActionGroup(this); @@ -646,6 +650,8 @@ void ApplicationWindow::CreateMenus(void) menu->addAction(fixAngleAct); menu->addAction(fixLengthAct); menu->addAction(rotateAct); + menu->addAction(connectAct); + menu->addAction(disconnectAct); menu->addSeparator(); menu->addAction(deleteAct); menu->addSeparator(); @@ -687,6 +693,8 @@ void ApplicationWindow::CreateToolbars(void) toolbar->addAction(fixLengthAct); toolbar->addAction(rotateAct); toolbar->addAction(deleteAct); + toolbar->addAction(connectAct); + toolbar->addAction(disconnectAct); toolbar->addSeparator(); toolbar->addAction(addLineAct); toolbar->addAction(addCircleAct); diff --git a/src/applicationwindow.h b/src/applicationwindow.h index 0db2265..8322abc 100644 --- a/src/applicationwindow.h +++ b/src/applicationwindow.h @@ -89,6 +89,8 @@ class ApplicationWindow: public QMainWindow QAction * zoomOutAct; QAction * snapToGridAct; QAction * groupAct; + QAction * connectAct; + QAction * disconnectAct; }; #endif // __APPLICATIONWINDOW_H__ diff --git a/src/container.cpp b/src/container.cpp index e909c04..757828a 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -72,21 +72,18 @@ Container & Container::operator=(const Container & from) QRectF boundary; for(std::vector::iterator i=objects.begin(); i!=objects.end(); i++) -// for(int i=0; i<(int)objects.size(); i++) { -#if 0 -//printf("Container: About to draw (object = $%X)\n", objects[i]); - objects[i]->Draw(painter); - bounds = bounds.united(objects[i].RectangularExtents()); -#else (*i)->Draw(painter); boundary = boundary.united((*i)->Extents()); -#endif } - if (state == OSSelected) + if ((state == OSSelected) || hit) { - painter->SetPen(QPen(Qt::magenta, 2.0, Qt::DashLine)); + 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->DrawRect(boundary); } @@ -98,6 +95,7 @@ Container & Container::operator=(const Container & from) return position; } + /* We need at least *three* handles for this object: - one for moving @@ -163,7 +161,11 @@ printf("Container::Collided: Deleting object ($%X)\n", *i); state = (collision ? OSSelected : OSInactive); if (state == OSSelected) + { DeselectAll(); + dragging = true; + oldPoint = point; + } } return collision; @@ -191,34 +193,62 @@ class so that we can leverage that stuff here as well. // every object for collision. /*virtual*/ void Container::PointerMoved(Vector point) { + std::vector::iterator i; + if (!isTopLevelContainer) { // check for selection rectangle too + if (selectionInProgress) + { + if (selection.contains(Extents())) + state = OSSelected; + else + state = OSInactive; + return; + } - needUpdate = true; + // No need to do any checking if we're already selected... +// if (state == OSSelected) +// return; - for(std::vector::iterator i=objects.begin(); i!=objects.end(); i++) +// 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; +// state = OSSelected; +// return; + hit = true; + break; } } - state = OSInactive; + needUpdate = (oldHit != hit ? true : false); +// state = oldState; + + if (dragging) + { + Vector delta = point - oldPoint; + + for(i=objects.begin(); i!=objects.end(); i++) + (*i)->Translate(delta); + + oldPoint = point; + needUpdate = true; + } + return; } -// objectWasDragged = true; -//printf("CONTAINER: PointerMoved()\n"); - for(std::vector::iterator i=objects.begin(); i!=objects.end(); i++) -// for(int i=0; i<(int)objects.size(); i++) { -//// if (objects[i]->GetState() == OSSelected) -// objects[i]->PointerMoved(point); +// if (objects[i]->GetState() == OSSelected) (*i)->PointerMoved(point); } @@ -229,6 +259,12 @@ class so that we can leverage that stuff here as well. /*virtual*/ void Container::PointerReleased(void) { + if (!isTopLevelContainer) + { + dragging = false; + return; + } +#if 0 dragging = false; draggingHandle1 = false; draggingHandle2 = false; @@ -236,15 +272,19 @@ class so that we can leverage that stuff here as well. // 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::iterator i=objects.begin(); i!=objects.end(); i++) + (*i)->PointerReleased(); } @@ -425,17 +465,10 @@ void Container::ResizeAllDimensions(double newSize) { for(std::vector::iterator i=objects.begin(); i!=objects.end(); i++) { -// Object * object = *i; - if ((*i)->type == OTDimension) -// if (object->type == OTDimension) - { ((Dimension *)(*i))->size = newSize; - } if ((*i)->type == OTContainer) - { ((Container *)(*i))->ResizeAllDimensions(newSize); - } } } @@ -443,13 +476,15 @@ void Container::ResizeAllDimensions(double newSize) /*virtual*/ void Container::Enumerate(FILE * file) { // Only put "CONTAINER" markers if *not* the top level container - if (parent != NULL) +// if (parent != NULL) + if (!isTopLevelContainer) fprintf(file, "CONTAINER\n"); for(uint i=0; iEnumerate(file); - if (parent != NULL) +// if (parent != NULL) + if (!isTopLevelContainer) fprintf(file, "ENDCONTAINER\n"); } diff --git a/src/container.h b/src/container.h index 6be759d..fd574da 100644 --- a/src/container.h +++ b/src/container.h @@ -44,6 +44,7 @@ class Container: public Object bool draggingHandle1; bool draggingHandle2; bool objectWasDragged; + bool hit; }; #endif // __CONTAINER_H__ diff --git a/src/dimension.cpp b/src/dimension.cpp index 2b49ff8..6f561be 100644 --- a/src/dimension.cpp +++ b/src/dimension.cpp @@ -92,10 +92,14 @@ I believe they are pixels. // Calculate whether or not the arrowheads are too crowded to put inside // the extension lines. 9.0 is the length of the arrowhead. - double t = Vector::Parameter(position, endpoint, endpoint - (unit * 9.0 * size)); +// double t = Vector::Parameter(position, endpoint, endpoint - (unit * 9.0 * size)); +// double t = Vector::Parameter(position, endpoint, position + (unit * 9.0 * size)); + double t = Vector::Parameter(endpoint, position, position + (unit * 9.0 * size)); //printf("Dimension::Draw(): t = %lf\n", t); - if (t > 0.42) +// On the screen, it's acting like this is actually 58%... +// This is correct, we want it to happen at > 50% + if (t > 0.58) { // Draw main dimension line + arrowheads painter->DrawLine(p1, p2); diff --git a/src/line.cpp b/src/line.cpp index aa5054b..9054bbc 100644 --- a/src/line.cpp +++ b/src/line.cpp @@ -132,8 +132,11 @@ Line::~Line() { // We can assume this, since this is a mouse down event here. objectWasDragged = false; + SaveState(); HitTest(point); +// return StateChanged(); +// this is shite. this should be checked for in the 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)) @@ -300,8 +303,10 @@ Like so: #if 0 if (selection.normalized().contains(Extents())) #else - if (selection.normalized().contains(position.x, position.y) - && selection.normalized().contains(endpoint.x, endpoint.y)) +// if (selection.normalized().contains(position.x, position.y) +// && selection.normalized().contains(endpoint.x, endpoint.y)) + if (selection.contains(position.x, position.y) + && selection.contains(endpoint.x, endpoint.y)) #endif state = OSSelected; else @@ -310,9 +315,11 @@ Like so: return; } - // 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); + // 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) + SaveState(); + HitTest(point); + needUpdate = StateChanged(); objectWasDragged = (draggingLine | draggingHandle1 | draggingHandle2); @@ -363,16 +370,6 @@ try to do the right thing, most of the time. :-) Vector point1 = (draggingHandle1 ? endpoint : position); Vector point2 = (draggingHandle1 ? position : endpoint); -#if 0 - Vector current(point2, point1); - Vector v = current.Unit() * length; - Vector v2 = point1 + v; - - //bleh - if (!Object::fixedLength) - v2 = point2; -#endif - if (Object::fixedAngle) { // Here we calculate the component of the current vector along the fixed angle. @@ -424,22 +421,21 @@ the horizontal line or vertical line that intersects from the current mouse posi } 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. :-) + // Calculate the new angle, just in case on the next move it turns + // out to be fixed. :-) angle = Vector(endpoint - position).Unit(); } } @@ -448,14 +444,6 @@ the horizontal line or vertical line that intersects from the current mouse posi 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; } @@ -463,28 +451,23 @@ about keeping track of old states... /*virtual*/ bool Line::HitTest(Point point) { - SaveState(); +// 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; + double t = Vector::Parameter(position, endpoint, point); + double 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()); + // The parameter "t" on the vector lineSegment is where the normal of + // lineSegment coincides with point. If t < 0, the normal lies beyond the + // 1st endpoint. If t > 1, then the normal lies beyond the 2nd endpoint. We + // only calculate the length of the normal between the point and the + // lineSegment when the parameter is between 0 and 1. - // 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. @@ -502,9 +485,15 @@ about keeping track of old states... // 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 !!!" -// [FIXED] + 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() * Painter::zoom) < 8.0) @@ -512,7 +501,8 @@ about keeping track of old states... else if ((distance * Painter::zoom) < 5.0) hitLine = true; - return StateChanged(); + return (hitPoint1 | hitPoint2 | hitLine ? true : false); +// return StateChanged(); } @@ -579,6 +569,23 @@ you then *definitely* do not want them to have the same reference number. } +/*virtual*/ void Line::Translate(Vector amount) +{ + position += amount; + endpoint += amount; +} + + +/*virtual*/ void Line::Rotate(Vector point, double angle) +{ +} + + +/*virtual*/ void Line::Scale(Vector point, double amount) +{ +} + + void Line::SetDimensionOnLine(Dimension * dimension/*=NULL*/) { // If they don't pass one in, create it for the caller. diff --git a/src/line.h b/src/line.h index fb17f2e..3d46551 100644 --- a/src/line.h +++ b/src/line.h @@ -23,6 +23,9 @@ class Line: public Object virtual Vector GetPointAtParameter(double parameter); virtual QRectF Extents(void); // virtual ObjectType Type(void); + virtual void Translate(Vector); + virtual void Rotate(Vector, double); + virtual void Scale(Vector, double); void SetDimensionOnLine(Dimension * d = 0); Object * FindAttachedDimension(void); diff --git a/src/object.cpp b/src/object.cpp index 99d73ce..523601d 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -189,6 +189,21 @@ printf("Object: Destroyed!\n"); #endif +/*virtual*/ void Object::Translate(Vector) +{ +} + + +/*virtual*/ void Object::Rotate(Vector, double) +{ +} + + +/*virtual*/ void Object::Scale(Vector, double) +{ +} + + ObjectState Object::GetState(void) { return state; diff --git a/src/object.h b/src/object.h index 532723a..513b677 100644 --- a/src/object.h +++ b/src/object.h @@ -41,6 +41,9 @@ class Object virtual void DisconnectAll(Object *); virtual QRectF Extents(void); // virtual ObjectType Type(void);// = 0; // Pure virtual, must be implemented + virtual void Translate(Vector); + virtual void Rotate(Vector, double); + virtual void Scale(Vector, double); ObjectState GetState(void); void Reparent(Object *); // Dimension * GetAttachedDimension(void); diff --git a/src/vector.cpp b/src/vector.cpp index ab1433f..23bd783 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -248,7 +248,15 @@ double Vector::Parameter(Vector v1, Vector v2, Vector p) double magnitude = lineSegment.Magnitude(); Vector pointSegment = p - v1; double t = lineSegment.Dot(pointSegment) / (magnitude * magnitude); - return t; } + +// Return the normal to the linesegment formed by the passed in points. +// (Not sure which is head or tail, or which hand the normal lies) +/*static*/ Vector Vector::Normal(Vector v1, Vector v2) +{ + Vector v = (v2 - v1).Unit(); + return Vector(-v.y, v.x); +} + diff --git a/src/vector.h b/src/vector.h index 0e7fc86..6c0ad20 100644 --- a/src/vector.h +++ b/src/vector.h @@ -48,6 +48,7 @@ class Vector static double Dot(Vector v1, Vector v2); static double Magnitude(Vector v1, Vector v2); static double Parameter(Vector v1, Vector v2, Vector p); + static Vector Normal(Vector v1, Vector v2); public: double x, y, z;