From: Shamus Hammons Date: Sun, 5 Feb 2017 02:07:58 +0000 (-0600) Subject: Added object pane, grouping, load/save functionality. X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?p=architektonas;a=commitdiff_plain;h=e78daf62eb771ee29a59035d16cf63c1e6ebe144 Added object pane, grouping, load/save functionality. There's probably a bunch more things that I'm forgetting, but that's what happens when you code like a sumbitch and then forget to commit it. :-P --- diff --git a/architektonas.pro b/architektonas.pro index a82f9a0..5feda56 100644 --- a/architektonas.pro +++ b/architektonas.pro @@ -61,7 +61,9 @@ HEADERS = \ src/layeritemwidget.h \ src/main.h \ src/mathconstants.h \ + src/objectwidget.h \ src/painter.h \ + src/rect.h \ src/settingsdialog.h \ src/structs.h \ src/utils.h \ @@ -81,7 +83,9 @@ SOURCES = \ src/layerwidget.cpp \ src/layeritemwidget.cpp \ src/main.cpp \ + src/objectwidget.cpp \ src/painter.cpp \ + src/rect.cpp \ src/settingsdialog.cpp \ src/utils.cpp \ src/vector.cpp diff --git a/src/applicationwindow.cpp b/src/applicationwindow.cpp index ad83272..a4c39b7 100644 --- a/src/applicationwindow.cpp +++ b/src/applicationwindow.cpp @@ -35,6 +35,7 @@ #include "generaltab.h" #include "global.h" #include "layerwidget.h" +#include "objectwidget.h" #include "painter.h" #include "settingsdialog.h" #include "structs.h" @@ -75,11 +76,16 @@ ApplicationWindow::ApplicationWindow(): BlockWidget * bw = new BlockWidget; dock2->setWidget(bw); addDockWidget(Qt::RightDockWidgetArea, dock2); + QDockWidget * dock3 = new QDockWidget(tr("Object"), this); + ObjectWidget * ow = new ObjectWidget; + dock3->setWidget(ow); + addDockWidget(Qt::RightDockWidgetArea, dock3); // Needed for saveState() dock1->setObjectName("Layers"); dock2->setObjectName("Blocks"); + dock3->setObjectName("Object"); - // Create status bar + // Create status bar zoomIndicator = new QLabel("Grid: 12.0\" BU: Inch"); statusBar()->addPermanentWidget(zoomIndicator); statusBar()->showMessage(tr("Ready")); @@ -91,6 +97,8 @@ ApplicationWindow::ApplicationWindow(): connect(lw, SIGNAL(LayerDeleted(int)), drawing, SLOT(DeleteCurrentLayer(int))); connect(lw, SIGNAL(LayerToggled()), drawing, SLOT(HandleLayerToggle())); connect(lw, SIGNAL(LayersSwapped(int, int)), drawing, SLOT(HandleLayerSwap(int, int))); + + connect(drawing, SIGNAL(ObjectHovered(Object *)), ow, SLOT(ShowInfo(Object *))); } @@ -104,8 +112,9 @@ void ApplicationWindow::closeEvent(QCloseEvent * event) void ApplicationWindow::FileNew(void) { - // Should warn the user if drawing hasn't been saved... - drawing->document.objects.empty(); + // Should warn the user if drawing hasn't been saved... !!! FIX !!! + DeleteContents(drawing->document.objects); + drawing->document.objects.clear(); drawing->update(); documentName.clear(); setWindowTitle("Architektonas - Untitled"); @@ -133,7 +142,7 @@ void ApplicationWindow::FileOpen(void) return; } - Container container;//(Vector(0, 0)); + Container container; bool successful = FileIO::LoadAtnsFile(file, &container); fclose(file); @@ -143,10 +152,16 @@ void ApplicationWindow::FileOpen(void) msg.setText(QString(tr("Could not load file \"%1\"!")).arg(filename)); msg.setIcon(QMessageBox::Critical); msg.exec(); + // Make sure to delete any hanging objects in the container... + DeleteContents(container.objects); return; } -printf("FileOpen: container size = %li\n", container.objects.size()); +//printf("FileOpen: container size = %li\n", container.objects.size()); + // Keep memory leaks from happening by getting rid of the old document + DeleteContents(drawing->document.objects); + // We can do this because the vector is just a bunch of pointers to our + // Objects, and the Containers (non-empty) can be moved way with no problem. drawing->document = container; drawing->update(); documentName = filename; @@ -181,7 +196,8 @@ void ApplicationWindow::FileSave(void) msg.setText(QString(tr("Could not save file \"%1\"!")).arg(documentName)); msg.setIcon(QMessageBox::Critical); msg.exec(); - // In this case, we should unlink the created file, since it's not right... + // In this case, we should unlink the created file, since it's not + // right... // unlink(documentName.toUtf8().data()); QFile::remove(documentName); return; @@ -227,9 +243,9 @@ void ApplicationWindow::DeleteTool(void) { // For this tool, we check first to see if anything is selected. If so, we // delete those and *don't* select the delete tool. - if (drawing->numSelected > 0) +// if (drawing->numSelected > 0) + if (drawing->select.size() > 0) { -// drawing->DeleteSelectedItems(); DeleteSelectedObjects(drawing->document.objects); drawing->update(); deleteAct->setChecked(false); @@ -507,8 +523,9 @@ else // Need the parent of the group, we're assuming here that the parent is // the drawing's document. Does it matter? Maybe... - // Could just stipulate that grouping like this only takes place where the - // parent of the group is the drawing's document. Makes life much simpler. + // Could just stipulate that grouping like this only takes place where + // the parent of the group is the drawing's document. Makes life much + // simpler. ((Container *)object)->SelectAll(); ((Container *)object)->MoveContentsTo(&(drawing->document)); drawing->document.Delete(object); @@ -526,6 +543,70 @@ else statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(itemsSelected)); } #else + int numSelected = drawing->select.size(); + + // If nothing selected, do nothing + if (numSelected == 0) + { + statusBar()->showMessage(tr("No objects selected to make a group from.")); + return; + } + + // If it's a group that's selected, ungroup it and leave the objects in a + // selected state + if (numSelected == 1) + { + Object * obj = (Object *)drawing->select[0]; + + if (obj->type != OTContainer) + { + statusBar()->showMessage(tr("A group requires two or more selected objects.")); + return; + } + + // Need the parent of the group, we're assuming here that the parent is + // the drawing's document. Does it matter? Maybe... + // Could just stipulate that grouping like this only takes place where + // the parent of the group is the drawing's document. Makes life much + // simpler. +// ((Container *)object)->SelectAll(); +// ((Container *)object)->MoveContentsTo(&(drawing->document)); +// drawing->document.Delete(object); + Container * c = (Container *)obj; +//printf("Ungroup: container size = %li\n", c->objects.size()); + SelectAll(c->objects); +//printf("Ungroup: document size = %li (pre-AddObjectsTo)\n", drawing->document.objects.size()); + RemoveSelectedObjects(drawing->document.objects); + AddObjectsTo(drawing->document.objects, c->objects); + drawing->select.clear(); + AddObjectsTo(drawing->select, c->objects); + delete c; + statusBar()->showMessage(tr("Objects ungrouped.")); +//printf("Ungroup: document size = %li\n", drawing->document.objects.size()); + } + // Otherwise, if it's a group of 2 or more objects (which can be groups too) + // group them and select the group + else if (numSelected > 1) + { +// Container * container = new Container(Vector(), &(drawing->document)); +// drawing->document.MoveSelectedContentsTo(container); +// drawing->document.Add(container); +// container->DeselectAll(); +// container->state = OSSelected; + + Container * c = new Container(); +// AddObjectsTo(c->objects, drawing->select); +// RemoveSelectedObjects(drawing->document.objects); + MoveSelectedObjectsTo(c->objects, drawing->document.objects); + drawing->document.objects.push_back(c); + ClearSelected(c->objects); + c->selected = true; + c->layer = Global::currentLayer; + drawing->select.clear(); + drawing->select.push_back(c); + statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(numSelected)); +//printf("Group: document size = %li\n", drawing->document.objects.size()); + } #endif drawing->update(); diff --git a/src/drawingview.cpp b/src/drawingview.cpp index 0e563e7..1fd0482 100644 --- a/src/drawingview.cpp +++ b/src/drawingview.cpp @@ -41,19 +41,15 @@ #define BACKGROUND_MAX_SIZE 512 -// Class variable -//Container DrawingView::document(Vector(0, 0)); - DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent), // The value in the settings file will override this. - useAntialiasing(true), numSelected(0), numHovered(0), shiftDown(false), + useAntialiasing(true), /*numSelected(0),*/ numHovered(0), shiftDown(false), ctrlDown(false), gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE), - scale(1.0), offsetX(-10), offsetY(-10), + scale(1.0), offsetX(-10), offsetY(-10), document(true), gridPixels(0), collided(false), hoveringIntersection(false) { -// document.isTopLevelContainer = true; //wtf? doesn't work except in c++11??? document = { 0 }; setBackgroundRole(QPalette::Base); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -382,7 +378,7 @@ void DrawingView::paintEvent(QPaintEvent * /*event*/) // // Renders objects in the passed in vector // -void DrawingView::RenderObjects(Painter * painter, std::vector & v, int layer) +void DrawingView::RenderObjects(Painter * painter, std::vector & v, int layer, bool ignoreLayer/*= false*/) { std::vector::iterator i; @@ -392,7 +388,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int float scaledThickness = Global::scale * obj->thickness; // If the object isn't on the current layer being drawn, skip it - if (obj->layer != layer) + if (!ignoreLayer && (obj->layer != layer)) continue; if ((Global::tool == TTRotate) && ctrlDown && obj->selected) @@ -569,6 +565,30 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness); break; } + case OTSpline: + { + break; + } + case OTPolygon: + { + break; + } + case OTContainer: + { + // Containers require recursive rendering... + Container * c = (Container *)obj; + RenderObjects(painter, (*c).objects, layer); + +//printf("Container extents: <%lf, %lf>, <%lf, %lf>\nsize: %i\n", r.l, r.t, r.r, r.b, c->objects.size()); + // Containers also have special indicators showing they are selected + if (c->selected || c->hitObject) + { + Rect r = GetObjectExtents(obj); + painter->DrawRectCorners(r); + } + + break; + } default: break; } @@ -675,13 +695,12 @@ void DrawingView::ToolDraw(Painter * painter) } else { + painter->DrawCross(toolPoint[0]); double length = Vector::Magnitude(toolPoint[0], toolPoint[1]); -// painter->DrawLine(toolPoint[0], toolPoint[1]); -// painter->DrawHandle(toolPoint[1]); painter->SetBrush(QBrush(Qt::NoBrush)); painter->DrawEllipse(toolPoint[0], length, length); - QString text = tr("Radius: %1 in.");//\n") + QChar(0x2221) + tr(": %2"); - informativeText = text.arg(length);//.arg(absAngle); + QString text = tr("Radius: %1 in."); + informativeText = text.arg(length); } } else if (Global::tool == TTArc) @@ -740,17 +759,6 @@ void DrawingView::ToolDraw(Painter * painter) return; painter->DrawLine(toolPoint[0], toolPoint[1]); - // Likely we need a tool container for this... (now we do!) -#if 0 - if (ctrlDown) - { - painter->SetPen(0x00FF00, 2.0, LSSolid); - overrideColor = true; - } - - RenderObjects(painter, toolObjects); - overrideColor = false; -#endif double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES; QString text = QChar(0x2221) + QObject::tr(": %1"); @@ -772,19 +780,7 @@ void DrawingView::ToolDraw(Painter * painter) return; Point mirrorPoint = toolPoint[0] + Vector(toolPoint[1], toolPoint[0]); -// painter->DrawLine(toolPoint[0], toolPoint[1]); painter->DrawLine(mirrorPoint, toolPoint[1]); - // Likely we need a tool container for this... (now we do!) -#if 0 - if (ctrlDown) - { - painter->SetPen(0x00FF00, 2.0, LSSolid); - overrideColor = true; - } - - RenderObjects(painter, toolObjects); - overrideColor = false; -#endif double absAngle = (Vector(toolPoint[1] - toolPoint[0]).Angle()) * RADIANS_TO_DEGREES; @@ -1171,7 +1167,9 @@ void DrawingView::mousePressEvent(QMouseEvent * event) // Handle tool processing, if any if (Global::tool) { - if (Global::snapToGrid) + if (hoveringIntersection) + point = intersectionPoint; + else if (Global::snapToGrid) point = SnapPointToGrid(point); //Also, may want to figure out if hovering over a snap point on an object, @@ -1199,7 +1197,9 @@ void DrawingView::mousePressEvent(QMouseEvent * event) // Needed for grab & moving objects // We do it *after*... why? (doesn't seem to confer any advantage...) - if (Global::snapToGrid) + if (hoveringIntersection) + oldPoint = intersectionPoint; + else if (Global::snapToGrid) oldPoint = SnapPointToGrid(point); return; @@ -1232,8 +1232,9 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) if (event->buttons() & Qt::MiddleButton) { point = Vector(event->x(), event->y()); - // Since we're using Qt coords for scrolling, we have to adjust them here to - // conform to Cartesian coords, since the origin is using Cartesian. :-) + // Since we're using Qt coords for scrolling, we have to adjust them + // here to conform to Cartesian coords, since the origin is using + // Cartesian. :-) Vector delta(oldPoint, point); delta /= Global::zoom; delta.y = -delta.y; @@ -1257,7 +1258,9 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) // Handle object movement (left button down & over an object) if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool) { - if (Global::snapToGrid) + if (hoveringIntersection) + point = intersectionPoint; + else if (Global::snapToGrid) point = SnapPointToGrid(point); HandleObjectMovement(point); @@ -1273,9 +1276,6 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) if (numHovered > 1) { GetHovered(hover); - -// double t, u; -// int numIntersecting = Geometry::Intersects((Object *)hover[0], (Object *)hover[1], &t, &u); Geometry::Intersects((Object *)hover[0], (Object *)hover[1]); int numIntersecting = Global::numIntersectParams; double t = Global::intersectParam[0]; @@ -1313,10 +1313,30 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) } } +//this doesn't work down here for some reason... :-P +//could be because the object being moved is part of the intersection, and this is screwing things up. In which case, we need to exclude the moving object somehow from the hit test function... +#if 0 + // Handle object movement (left button down & over an object) + if ((event->buttons() & Qt::LeftButton) && numHovered && !Global::tool) + { + if (hoveringIntersection) + point = intersectionPoint; + else if (Global::snapToGrid) + point = SnapPointToGrid(point); + + HandleObjectMovement(point); + update(); + oldPoint = point; + return; + } +#endif + // Do tool handling, if any are active... if (Global::tool) { - if (Global::snapToGrid) + if (hoveringIntersection) + point = intersectionPoint; + else if (Global::snapToGrid) point = SnapPointToGrid(point); ToolHandler(ToolMouseMove, point); @@ -1335,8 +1355,8 @@ void DrawingView::mouseReleaseEvent(QMouseEvent * event) if (event->button() == Qt::LeftButton) { //We need to update especially if nothing collided and the state needs to change. !!! FIX !!! -//could set it up to use the document's update function (assumes that all object updates -//are being reported correctly: +//could set it up to use the document's update function (assumes that all object +//updates are being reported correctly: // if (document.NeedsUpdate()) // Do an update if collided with at least *one* object in the document // if (collided) @@ -1468,10 +1488,84 @@ Point DrawingView::SnapPointToGrid(Point point) } +Rect DrawingView::GetObjectExtents(Object * obj) +{ + // Default to empty rect, if object checks below fail for some reason + Rect rect; + + switch (obj->type) + { + case OTLine: + { + rect = Rect(obj->p[0], obj->p[1]); + break; + } + case OTCircle: + { + rect = Rect(obj->p[0], obj->p[0]); + rect.Expand(obj->radius[0]); + break; + } + case OTArc: + { + Arc * a = (Arc *)obj; + + double start = a->angle[0]; + double end = start + a->angle[1]; + rect = Rect(Point(cos(start), sin(start)), Point(cos(end), sin(end))); + + // If the end of the arc is before the beginning, add 360 degrees to it + if (end < start) + end += TAU; + + // Adjust the bounds depending on which axes are crossed + if ((start < QTR_TAU) && (end > QTR_TAU)) + rect.t = 1.0; + + if ((start < HALF_TAU) && (end > HALF_TAU)) + rect.l = -1.0; + + if ((start < THREE_QTR_TAU) && (end > THREE_QTR_TAU)) + rect.b = -1.0; + + if ((start < TAU) && (end > TAU)) + rect.r = 1.0; + + if ((start < (TAU + QTR_TAU)) && (end > (TAU + QTR_TAU))) + rect.t = 1.0; + + if ((start < (TAU + HALF_TAU)) && (end > (TAU + HALF_TAU))) + rect.l = -1.0; + + if ((start < (TAU + THREE_QTR_TAU)) && (end > (TAU + THREE_QTR_TAU))) + rect.b = -1.0; + + rect *= a->radius[0]; + rect.Translate(a->p[0]); + + break; + } + case OTContainer: + { + Container * c = (Container *)obj; + std::vector::iterator i = c->objects.begin(); + rect = GetObjectExtents((Object *)*i); + i++; + + for(; i!=c->objects.end(); i++) + rect |= GetObjectExtents((Object *)*i); + } + default: + break; + } + + return rect; +} + + void DrawingView::CheckObjectBounds(void) { std::vector::iterator i; - numSelected = 0; for(i=document.objects.begin(); i!=document.objects.end(); i++) { @@ -1566,9 +1660,6 @@ void DrawingView::CheckObjectBounds(void) default: break; } - - if (obj->selected) - numSelected++; } } @@ -1583,6 +1674,9 @@ bool DrawingView::HitTestObjects(Point point) { Object * obj = (Object *)(*i); + if (HitTest(obj, point)) + needUpdate = true; +#if 0 switch (obj->type) { case OTLine: @@ -1673,15 +1767,154 @@ bool DrawingView::HitTestObjects(Point point) break; } + case OTContainer: + { + // Containers must be recursively tested... + Container * c = (Container *)obj; + std::vector::iterator i; + + for(i=c->objects.begin(); i!=c->objects.end(); i++) + { + + } + } default: break; } +#endif if (obj->hovered) -// { + { numHovered++; //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered); -// } + emit(ObjectHovered(obj)); + } + } + + return needUpdate; +} + + +bool DrawingView::HitTest(Object * obj, Point point) +{ + bool needUpdate = false; + + switch (obj->type) + { + case OTLine: + { + bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHO = obj->hitObject; + obj->hitPoint[0] = obj->hitPoint[1] = obj->hitObject = false; + Vector lineSegment = obj->p[1] - obj->p[0]; + Vector v1 = point - obj->p[0]; + Vector v2 = point - obj->p[1]; + double t = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], point); + double distance; + + 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() * Global::zoom) < 8.0) + obj->hitPoint[0] = true; + else if ((v2.Magnitude() * Global::zoom) < 8.0) + obj->hitPoint[1] = true; + else if ((distance * Global::zoom) < 5.0) + obj->hitObject = true; + + obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitObject ? true : false); + + if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHO != obj->hitObject)) + needUpdate = true; + + break; + } + case OTCircle: + { + bool oldHP = obj->hitPoint[0], oldHO = obj->hitObject; + obj->hitPoint[0] = obj->hitObject = false; + double length = Vector::Magnitude(obj->p[0], point); + + if ((length * Global::zoom) < 8.0) + obj->hitPoint[0] = true; + else if ((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) + obj->hitObject = true; + + obj->hovered = (obj->hitPoint[0] || obj->hitObject ? true : false); + + if ((oldHP != obj->hitPoint[0]) || (oldHO != obj->hitObject)) + needUpdate = true; + + break; + } + case OTArc: + { + bool oldHP0 = obj->hitPoint[0], oldHP1 = obj->hitPoint[1], oldHP2 = obj->hitPoint[2], oldHO = obj->hitObject; + obj->hitPoint[0] = obj->hitPoint[1] = obj->hitPoint[2] = obj->hitObject = false; + double length = Vector::Magnitude(obj->p[0], point); + double angle = Vector::Angle(obj->p[0], point); + + // Make sure we get the angle in the correct spot + if (angle < obj->angle[0]) + angle += TAU; + + // Get the span that we're pointing at... + double span = angle - obj->angle[0]; + + // N.B.: Still need to hit test the arc start & arc span handles... + double spanAngle = obj->angle[0] + obj->angle[1]; + Point handle1 = obj->p[0] + (Vector(cos(obj->angle[0]), sin(obj->angle[0])) * obj->radius[0]); + Point handle2 = obj->p[0] + (Vector(cos(spanAngle), sin(spanAngle)) * obj->radius[0]); + double length2 = Vector::Magnitude(point, handle1); + double length3 = Vector::Magnitude(point, handle2); + + if ((length * Global::zoom) < 8.0) + obj->hitPoint[0] = true; + else if ((length2 * Global::zoom) < 8.0) + obj->hitPoint[1] = true; + else if ((length3 * Global::zoom) < 8.0) + obj->hitPoint[2] = true; + else if (((fabs(length - obj->radius[0]) * Global::zoom) < 2.0) && (span < obj->angle[1])) + obj->hitObject = true; + + obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitObject ? true : false); + + if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHO != obj->hitObject)) + needUpdate = true; + + break; + } + case OTContainer: + { + // Containers must be recursively tested... + Container * c = (Container *)obj; + c->hitObject = false; + c->hovered = false; + std::vector::iterator i; + + for(i=c->objects.begin(); i!=c->objects.end(); i++) + { + Object * cObj = (Object *)*i; + + if (HitTest(cObj, point)) + needUpdate = true; + + if (cObj->hitObject == true) + c->hitObject = true; + + if (cObj->hovered == true) + c->hovered = true; + } + + break; + } + default: + break; } return needUpdate; @@ -1791,90 +2024,15 @@ void DrawingView::HandleObjectMovement(Point point) informativeText = text.arg(obj->radius[0], 0, 'd', 4); } + break; + case OTContainer: + // This is shitty, but works for now until I can code up something + // nicer :-) + TranslateObject(obj, delta); + break; default: break; } } - - -#if 0 - // This returns true if we've moved over an object... - if (document.PointerMoved(point)) // <-- This - // This is where the object would do automagic dragging & shit. Since we don't - // do that anymore, we need a strategy to handle it. - { - -/* -Now objects handle mouse move snapping as well. The code below mainly works only -for tools; we need to fix it so that objects work as well... - -There's a problem with the object point snapping in that it's dependent on the -order of the objects in the document. Most likely this is because it counts the -selected object last and thus fucks up the algorithm. Need to fix this... - - -*/ - // Do object snapping here. Grid snapping on mouse down is done in the - // objects themselves, only because we have to hit test the raw point, - // not the snapped point. There has to be a better way...! - if (document.penultimateObjectHovered) - { - // Two objects are hovered, see if we have an intersection point - if ((document.lastObjectHovered->type == OTLine) && (document.penultimateObjectHovered->type == OTLine)) - { - double t; - int n = Geometry::Intersects((Line *)document.lastObjectHovered, (Line *)document.penultimateObjectHovered, &t); - - if (n == 1) - { - Global::snapPoint = document.lastObjectHovered->GetPointAtParameter(t); - Global::snapPointIsValid = true; - } - } - else if ((document.lastObjectHovered->type == OTCircle) && (document.penultimateObjectHovered->type == OTCircle)) - { - Point p1, p2; - int n = Geometry::Intersects((Circle *)document.lastObjectHovered, (Circle *)document.penultimateObjectHovered, 0, 0, 0, 0, &p1, &p2); - - if (n == 1) - { - Global::snapPoint = p1; - Global::snapPointIsValid = true; - } - else if (n == 2) - { - double d1 = Vector(point, p1).Magnitude(); - double d2 = Vector(point, p2).Magnitude(); - - if (d1 < d2) - Global::snapPoint = p1; - else - Global::snapPoint = p2; - - Global::snapPointIsValid = true; - } - } - } -// else -// { - // Otherwise, it was a single object hovered... -// } - } - - if (toolAction) - { - if (Global::snapToGrid) - point = Global::SnapPointToGrid(point); - - // We always snap to object points, and they take precendence over - // grid points... - if (Global::snapPointIsValid) - point = Global::snapPoint; - - toolAction->MouseMoved(point); - } -#else -#endif - diff --git a/src/drawingview.h b/src/drawingview.h index cf70e40..780c812 100644 --- a/src/drawingview.h +++ b/src/drawingview.h @@ -3,6 +3,7 @@ #include #include +#include "rect.h" #include "structs.h" enum { ToolMouseDown, ToolMouseMove, ToolMouseUp, ToolKeyDown, ToolKeyUp, ToolCleanup }; @@ -20,7 +21,7 @@ class DrawingView: public QWidget void SetGridSize(uint32_t); void UpdateGridBackground(void); Point SnapPointToGrid(Point); - void RenderObjects(Painter *, std::vector &, int); + void RenderObjects(Painter *, std::vector &, int, bool ignoreLayer = false); void AddHoveredToSelection(void); void GetSelection(std::vector &); void GetHovered(std::vector &); @@ -31,8 +32,10 @@ class DrawingView: public QWidget void ArcHandler(int, Point); void RotateHandler(int, Point); void MirrorHandler(int, Point); + Rect GetObjectExtents(Object *); void CheckObjectBounds(void); bool HitTestObjects(Point); + bool HitTest(Object *, Point); void HandleObjectMovement(Point); public slots: @@ -40,6 +43,9 @@ class DrawingView: public QWidget void HandleLayerToggle(void); void HandleLayerSwap(int, int); + signals: + void ObjectHovered(Object *); + protected: void paintEvent(QPaintEvent * event); void resizeEvent(QResizeEvent * event); @@ -56,7 +62,7 @@ class DrawingView: public QWidget public: bool useAntialiasing; - uint32_t numSelected; +// uint32_t numSelected; uint32_t numHovered; bool shiftDown; bool ctrlDown; diff --git a/src/fileio.cpp b/src/fileio.cpp index e0028cd..1002dca 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -108,42 +108,53 @@ OTHER CONSIDERATIONS: */ -enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc, - OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile }; +//enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc, +// OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile }; +enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile }; -/*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * object) +// Instantiate class variables +/*static*/ int FileIO::objectFileType = OTFObject; + + +/*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c) { - /* Approach: loop through the container, doing a depth-first traversal. Any extra - containers found are looped through until there aren't any more down, then - ordinary objects are described. This can be handled by a virtual Object function - that reports the object by itself if it's a non-Container, otherwise it - enumerates all objects within itself. */ + /* Approach: loop through the container, doing a depth-first traversal. Any + extra containers found are looped through until there aren't any more + down, then ordinary objects are described. This can be handled by a + virtual Object function that reports the object by itself if it's a non- + Container, otherwise it enumerates all objects within itself. */ - fprintf(file, "ARCHITEKTONAS DRAWING V1.0\n"); + fprintf(file, "ARCHITEKTONAS DRAWING V1.1\n"); #if 0 object->Enumerate(file); - fprintf(file, "END\n"); - return true; #else - return false; + WriteObjectToFile(file, (Object *)c); #endif + fprintf(file, "END\n"); + return true; } /*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing) { -// char buffer[256]; float version; fscanf(file, "ARCHITEKTONAS DRAWING V%f", &version); //printf("Load: version = %f\n", version); - if (version != 1.0) - return false; - /* Approach: read each object in the file, one by one. If the object is a Container, - add objects to it until an "endContainer" marker is found. This will require a - stack to maintain the current Container. */ +// if (version != 1.0) +// return false; + + if (version == 1.0) + return LoadVersion1_0(file, drawing); + else if (version == 1.1) + return LoadVersion1_1(file, drawing); + + return false; + /* Approach: read each object in the file, one by one. If the object is a + Container, add objects to it until an "endContainer" marker is found. + This will require a stack to maintain the current Container. */ #if 0 std::vector containerStack; @@ -158,9 +169,10 @@ enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc, return false; // object->type down below can be replaced with objType. - // Above could be: bool FileIO::GetObjectFromFile(FILE *, Object *, ObjectType *); - // where the return value tells if it's a valid object, Object * returns the - // reconstructed object and ObjectType * returns the object type. + // Above could be: bool FileIO::GetObjectFromFile(FILE *, Object *, + // ObjectType *); where the return value tells if it's a valid object, + // Object * returns the reconstructed object and ObjectType * returns + // the object type. if (objectType == OTFEndOfFile) { @@ -193,21 +205,114 @@ enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc, } -/*static*/ bool FileIO::GetObjectFromFile(FILE * file, Object * parent, Object ** object, int * objectType) +/*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing) +{ + // Approach: read each object in the file, one by one. If the object is a + // Container, add objects to it until an "endContainer" marker is found. + // This will require a stack to maintain the current Container. + std::vector containerStack; + Container * currentTopContainer = drawing; + + while (!feof(file)) + { + // Reconstruct the object + Object * obj = GetObjectFromFile(file); + + // objectFileType is set in GetObjectFromFile()... + if (objectFileType == OTFObject) + { + if (obj == NULL) + return false; + + currentTopContainer->objects.push_back(obj); +//printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size()); + + // If the object is a container, push current TLC on the stack and + // set it as the new TLC + if (obj->type == OTContainer) + { + containerStack.push_back(currentTopContainer); + currentTopContainer = (Container *)obj; + } + } + else if (objectFileType == OTFContainerEnd) + { + // Container is done, so pop the stack to get back the previous TLC + currentTopContainer = containerStack.back(); + containerStack.pop_back(); + } + else if (objectFileType == OTFEndOfFile) + { +//printf("Load: container size = %li\n", drawing->objects.size()); + return true; + } + } + + return false; +} + + +/*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing) +{ + // Approach: read each object in the file, one by one. If the object is a + // Container, add objects to it until an "endContainer" marker is found. + // This will require a stack to maintain the current Container. + std::vector containerStack; + Container * currentTopContainer = drawing; + + while (!feof(file)) + { + // Reconstruct the object (extended format!) + Object * obj = GetObjectFromFile(file, true); + + // objectFileType is set in GetObjectFromFile()... + if (objectFileType == OTFObject) + { + if (obj == NULL) + return false; + + currentTopContainer->objects.push_back(obj); +//printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size()); + + // If the object is a container, push current TLC on the stack and + // set it as the new TLC + if (obj->type == OTContainer) + { + containerStack.push_back(currentTopContainer); + currentTopContainer = (Container *)obj; + } + } + else if (objectFileType == OTFContainerEnd) + { + // Container is done, so pop the stack to get back the previous TLC + currentTopContainer = containerStack.back(); + containerStack.pop_back(); + } + else if (objectFileType == OTFEndOfFile) + { +//printf("Load: container size = %li\n", drawing->objects.size()); + return true; + } + } + + return false; +} + + +/*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/) { -#if 0 char buffer[256]; int foundLayer = 0; - int num = fscanf(file, "%s", buffer); - bool recognized = false; - *object = NULL; + /*int num =*/ fscanf(file, "%s", buffer); + Object * obj = NULL; + objectFileType = OTFObject; //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer); // The following fugliness is for troubleshooting. Can remove later. if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0)) { errno = 0; - num = fscanf(file, " %i ", &foundLayer); + /*num =*/ fscanf(file, " %i ", &foundLayer); //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer); if (errno) { @@ -230,65 +335,116 @@ if (errno) } } + // Need to add pen attributes as well... do that for v1.1 :-P if (strcmp(buffer, "LINE") == 0) { -//printf(" Found LINE.\n"); - recognized = true; - Vector v1, v2; - fscanf(file, "(%lf,%lf) (%lf,%lf)", &v1.x, &v1.y, &v2.x, &v2.y); -//printf(" Number of params recognized: %i\n", n); - *object = new Line(v1, v2, parent); - *objectType = OTFLine; + Point p1, p2; + fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y); + obj = (Object *)new Line(p1, p2); } else if (strcmp(buffer, "CIRCLE") == 0) { - recognized = true; - Vector v; + Point p; double r; - fscanf(file, "(%lf,%lf) %lf", &v.x, &v.y, &r); - *object = new Circle(v, r, parent); - *objectType = OTFCircle; + fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r); + obj = (Object *)new Circle(p, r); } else if (strcmp(buffer, "ARC") == 0) { - recognized = true; - Vector v; + Point p; double r, a1, a2; - fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &v.x, &v.y, &r, &a1, &a2); - *object = new Arc(v, r, a1, a2, parent); - *objectType = OTFArc; + fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2); + obj = (Object *)new Arc(p, r, a1, a2); } else if (strcmp(buffer, "DIMENSION") == 0) { - recognized = true; - Vector v1, v2; + Point p1, p2; DimensionType type; - fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &v1.x, &v1.y, &v2.x, &v2.y, &type); - *object = new Dimension(v1, v2, type, parent); - *objectType = OTFDimension; + fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type); + obj = (Object *)new Dimension(p1, p2, type); } else if (strcmp(buffer, "CONTAINER") == 0) { - recognized = true; - *objectType = OTFContainer; + obj = (Object *)new Container(); +// objectFileType = OTFContainer; } else if (strcmp(buffer, "ENDCONTAINER") == 0) { - recognized = true; - *objectType = OTFContainerEnd; + objectFileType = OTFContainerEnd; } else if (strcmp(buffer, "END") == 0) { - recognized = true; - *objectType = OTFEndOfFile; + objectFileType = OTFEndOfFile; } - if (*object) - (*object)->layer = foundLayer; + if (obj != NULL) + { + obj->layer = foundLayer; - return recognized; -#else - return false; -#endif + if (extended && (obj->type != OTContainer)) + { + fscanf(file, " (%i, %f, %i)", &obj->color, &obj->thickness, &obj->style); + } + } + + return obj; +} + + +/*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj) +{ + // Sanity check + if (obj == NULL) + return false; + + switch (obj->type) + { + case OTLine: + fprintf(file, "LINE %i (%lf,%lf) (%lf,%lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y); + break; + case OTCircle: + fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]); + break; + case OTEllipse: + break; + case OTArc: + fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]); + break; + case OTPolygon: + break; + case OTDimension: +// fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType); + fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype); + break; + case OTSpline: + break; + case OTText: + fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str()); + break; + case OTContainer: + { + Container * c = (Container *)obj; + + if (c->topLevel == false) + fprintf(file, "CONTAINER %i\n", obj->layer); + + std::vector::iterator i; + + for(i=c->objects.begin(); i!=c->objects.end(); i++) + WriteObjectToFile(file, (Object *)*i); + + if (c->topLevel == false) + fprintf(file, "ENDCONTAINER\n"); + + break; + } + default: + break; + } + + if (obj->type != OTContainer) + fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style); + + return true; } diff --git a/src/fileio.h b/src/fileio.h index d4fc9d5..8aa7511 100644 --- a/src/fileio.h +++ b/src/fileio.h @@ -3,8 +3,6 @@ #include #include "structs.h" -//class Container; -//class Object; // NB: The methods in this class are all static, so there's no need to // instantiate an object of this type to use its functions. @@ -16,7 +14,13 @@ class FileIO static bool LoadAtnsFile(FILE *, Container *); private: - static bool GetObjectFromFile(FILE *, Object *, Object **, int *); + static bool LoadVersion1_0(FILE *, Container *); + static bool LoadVersion1_1(FILE *, Container *); +// static bool GetObjectFromFile(FILE *, Object *, Object **, int *); + static Object * GetObjectFromFile(FILE *, bool extended = false); + static bool WriteObjectToFile(FILE *, Object *); + + static int objectFileType; }; #endif // __FILEIO_H__ diff --git a/src/geometry.cpp b/src/geometry.cpp index b915897..e5a6af2 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -255,14 +255,12 @@ void Geometry::CheckLineToCircleIntersection(Object * l, Object * c) // Radius is the hypotenuse, so we have to use c² = a² + b² => a² = c² - b² double perpendicularLength = sqrt((c->radius[0] * c->radius[0]) - (distance * distance)); - // Now, find the points using the length, then check to see if they are on - // the line segment + // Now, find the intersection points using the length... Vector lineUnit = Vector(l->p[0], l->p[1]).Unit(); Point i1 = p + (lineUnit * perpendicularLength); Point i2 = p - (lineUnit * perpendicularLength); - // Now we have our intersection points, next we need to see if they are on - // the line segment... + // Next we need to see if they are on the line segment... double u = ParameterOfLineAndPoint(l->p[0], l->p[1], i1); double v = ParameterOfLineAndPoint(l->p[0], l->p[1], i2); diff --git a/src/objectwidget.cpp b/src/objectwidget.cpp new file mode 100644 index 0000000..c7e0cf6 --- /dev/null +++ b/src/objectwidget.cpp @@ -0,0 +1,117 @@ +// objectwidget.cpp: Object tweaking widget +// +// Part of the Architektonas Project +// (C) 2016 Underground Software +// See the README and GPLv3 files for licensing and warranty information +// +// JLH = James Hammons +// +// WHO WHEN WHAT +// --- ---------- ------------------------------------------------------------ +// JLH 11/07/2016 Created this file +// + +#include "objectwidget.h" +#include "mathconstants.h" + + +ObjectWidget::ObjectWidget(void): QWidget() +{ +#if 0 + QListWidget * qlw = new QListWidget; + QListWidgetItem * qli1 = new QListWidgetItem(qlw); + QListWidgetItem * qli2 = new QListWidgetItem(qlw); + QListWidgetItem * qli3 = new QListWidgetItem(qlw); + QListWidgetItem * qli4 = new QListWidgetItem(qlw); + QListWidgetItem * qli5 = new QListWidgetItem(qlw); +#endif + label = new QLabel; + +#if 0 + QPushButton * pb1 = new QPushButton("+"); + QPushButton * pb2 = new QPushButton("-"); + QPushButton * pb3 = new QPushButton("Edit"); + QPushButton * pb4 = new QPushButton("Import"); +#else + QToolButton * pb1 = new QToolButton; + QToolButton * pb2 = new QToolButton; + QToolButton * pb3 = new QToolButton; + QToolButton * pb4 = new QToolButton; + + pb1->setIcon(QIcon(":/res/layer-add.png")); + pb2->setIcon(QIcon(":/res/layer-delete.png")); + pb3->setIcon(QIcon(":/res/layer-edit.png")); + pb4->setIcon(QIcon(":/res/block-import.png")); + + pb1->setToolTip(tr("Add block")); + pb2->setToolTip(tr("Remove block")); + pb3->setToolTip(tr("Edit block")); + pb4->setToolTip(tr("Import block")); +#endif + + QHBoxLayout * hbox1 = new QHBoxLayout; + hbox1->addWidget(pb1); + hbox1->addWidget(pb2); + hbox1->addWidget(pb3); + hbox1->addWidget(pb4); + hbox1->addStretch(); + + QVBoxLayout * mainLayout = new QVBoxLayout; + mainLayout->addWidget(label); + mainLayout->addLayout(hbox1); + + setLayout(mainLayout); +} + + +ObjectWidget::~ObjectWidget() +{ +} + + +void ObjectWidget::ShowInfo(Object * obj) +{ + const char objName[OTCount][16] = { + "None", "Line", "Circle", "Ellipse", "Arc", "Polygon", "Dimension", "Spline", "Text", "Container" + }; + + // Sanity check + if (obj == NULL) + return; + + QString s = QString("%1

").arg(QString(objName[obj->type])); + + switch (obj->type) + { + case OTLine: + { + Vector line(obj->p[0], obj->p[1]); + s += QString("<%1, %2> to <%3, %4>
Length: %5
Angle: %6°
").arg(obj->p[0].x).arg(obj->p[0].y).arg(obj->p[1].x).arg(obj->p[1].y).arg(line.Magnitude()).arg(line.Angle() * RADIANS_TO_DEGREES); + break; + } + case OTCircle: + s += QString("Center: <%1, %2>
Radius: %3
").arg(obj->p[0].x).arg(obj->p[0].y).arg(obj->radius[0]); + break; + case OTEllipse: + break; + case OTArc: + s += QString("Center: <%1, %2>
Radius: %3
Start: %4°
End: %5°
").arg(obj->p[0].x).arg(obj->p[0].y).arg(obj->radius[0]).arg(obj->angle[0] * RADIANS_TO_DEGREES).arg(obj->angle[1] * RADIANS_TO_DEGREES); + break; + break; + case OTPolygon: + break; + case OTDimension: + break; + case OTSpline: + break; + case OTText: + break; + case OTContainer: + break; + default: + break; + } + + label->setText(s); +} + diff --git a/src/objectwidget.h b/src/objectwidget.h new file mode 100644 index 0000000..31b7be8 --- /dev/null +++ b/src/objectwidget.h @@ -0,0 +1,24 @@ +#ifndef __OBJECTWIDGET_H__ +#define __OBJECTWIDGET_H__ + +#include +#include "structs.h" + + +class ObjectWidget: public QWidget +{ + Q_OBJECT + + public: + ObjectWidget(void); + ~ObjectWidget(); + + public slots: + void ShowInfo(Object *); + + private: + QLabel * label; +}; + +#endif // __OBJECTWIDGET_H__ + diff --git a/src/painter.cpp b/src/painter.cpp index e8aaba1..0806811 100644 --- a/src/painter.cpp +++ b/src/painter.cpp @@ -17,12 +17,6 @@ #include "mathconstants.h" -// Set class variable defaults -//Vector Painter::origin(-10.0, -10.0); -//double Painter::zoom = 1.0; -//Vector Painter::screenSize(200.0, 200.0); - - Painter::Painter(QPainter * p/*= NULL*/): painter(p) { } @@ -148,19 +142,11 @@ void Painter::DrawAngledText(Vector center, double angle, QString text, double s float yOffset = -12.0 * Global::zoom * size; // Fix text so it isn't upside down... -#if 0 - if ((angle > PI * 0.5) && (angle < PI * 1.5)) - { - angle += PI; - yOffset = 12.0 * Global::zoom * size; - } -#else if ((angle > QTR_TAU) && (angle < THREE_QTR_TAU)) { angle += HALF_TAU; yOffset = 12.0 * Global::zoom * size; } -#endif textBox.translate(0, yOffset); painter->save(); @@ -199,6 +185,9 @@ void Painter::DrawTextObject(Point p, QString text, double size, double angle/*= void Painter::DrawArc(Vector center, double radius, double startAngle, double span) { + if (!painter) + return; + center = CartesianToQtCoords(center); // Need to multiply scalar quantities by the zoom factor as well... radius *= Global::zoom; @@ -212,6 +201,9 @@ void Painter::DrawArc(Vector center, double radius, double startAngle, double sp void Painter::DrawEllipse(Vector center, double axis1, double axis2) { + if (!painter) + return; + // Need to multiply scalar quantities by the zoom factor as well... center = CartesianToQtCoords(center); painter->drawEllipse(QPointF(center.x, center.y), axis1 * Global::zoom, axis2 * Global::zoom); @@ -222,6 +214,9 @@ void Painter::DrawEllipse(Vector center, double axis1, double axis2) // we don't want our object handle size to depend on the zoom level! void Painter::DrawHandle(Vector center) { + if (!painter) + return; + center = CartesianToQtCoords(center); painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine)); painter->setBrush(Qt::NoBrush); @@ -229,16 +224,62 @@ void Painter::DrawHandle(Vector center) } +// This function is for drawing feedback points without regard for zoom level; +// we don't want our feedback point size to depend on the zoom level! +void Painter::DrawCross(Vector point) +{ + if (!painter) + return; + + point = CartesianToQtCoords(point); + painter->setPen(QPen(Qt::red, 2.0, Qt::SolidLine)); + painter->drawLine(point.x - 8.0, point.y, point.x + 8.0, point.y); + painter->drawLine(point.x, point.y - 8.0, point.x, point.y + 8.0); +} + + +// This function is for drawing feedback points without regard for zoom level; +// we don't want our feedback point size to depend on the zoom level! +void Painter::DrawRectCorners(Rect rect) +{ + if (!painter) + return; + +// QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y)); + + Vector v1 = CartesianToQtCoords(Vector(rect.l, rect.t)); + Vector v2 = CartesianToQtCoords(Vector(rect.r, rect.b)); +// QRectF screenRect(QPointF(v1.x, v1.y), QPointF(v2.x, v2.y)); +// screenRect.adjust(-8, 8, 8, -8); // Left/top, right/bottom +// painter->drawRect(screenRect); + v1 += Vector(-8.0, -8.0); + v2 += Vector(+8.0, +8.0); + painter->setPen(QPen(Qt::red, 2.0, Qt::DashLine)); + painter->drawLine(v1.x, v1.y, v1.x + 24, v1.y); + painter->drawLine(v1.x, v1.y, v1.x, v1.y + 24); + painter->drawLine(v2.x, v1.y, v2.x - 24, v1.y); + painter->drawLine(v2.x, v1.y, v2.x, v1.y + 24); + painter->drawLine(v2.x, v2.y, v2.x - 24, v2.y); + painter->drawLine(v2.x, v2.y, v2.x, v2.y - 24); + painter->drawLine(v1.x, v2.y, v1.x + 24, v2.y); + painter->drawLine(v1.x, v2.y, v1.x, v2.y - 24); + +} + + // This function is for drawing object handles without regard for zoom level; // we don't want our object handle size to depend on the zoom level! void Painter::DrawArrowHandle(Vector center, double angle) { + if (!painter) + return; + center = CartesianToQtCoords(center); QPolygonF arrow; // Since we're drawing directly on the screen, the Y is inverted. So we use // the mirror of the angle. - double orthoAngle = -angle + QTR_TAU;//(PI / 2.0); + double orthoAngle = -angle + QTR_TAU; Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle)); Vector unit = Vector(cos(-angle), sin(-angle)); @@ -259,12 +300,15 @@ void Painter::DrawArrowHandle(Vector center, double angle) // we don't want our object handle size to depend on the zoom level! void Painter::DrawArrowToLineHandle(Vector center, double angle) { + if (!painter) + return; + DrawArrowHandle(center, angle); center = CartesianToQtCoords(center); // Since we're drawing directly on the screen, the Y is inverted. So we use // the mirror of the angle. - double orthoAngle = -angle + QTR_TAU;//(PI / 2.0); + double orthoAngle = -angle + QTR_TAU; Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle)); Vector unit = Vector(cos(-angle), sin(-angle)); @@ -363,7 +407,7 @@ void Painter::DrawArrowhead(Vector head, Vector tail, double size) // We draw the arrowhead aligned along the line from tail to head double angle = Vector(head - tail).Angle(); - double orthoAngle = angle + QTR_TAU;//(PI / 2.0); + double orthoAngle = angle + QTR_TAU; Vector orthogonal = Vector(cos(orthoAngle), sin(orthoAngle)); Vector unit = Vector(head - tail).Unit(); @@ -395,6 +439,9 @@ void Painter::DrawCrosshair(Vector point) void Painter::DrawInformativeText(QString text) { + if (!painter) + return; + painter->setFont(*Global::font); QRectF bounds = painter->boundingRect(QRectF(), Qt::AlignVCenter, text); bounds.moveTo(17.0, 17.0); diff --git a/src/painter.h b/src/painter.h index 5e868d7..4ff50b2 100644 --- a/src/painter.h +++ b/src/painter.h @@ -3,10 +3,9 @@ #include #include +#include "rect.h" #include "vector.h" -//#define SCREEN_ZOOM (1.0 / 4.0) - // Forward declarations class Painter @@ -26,6 +25,8 @@ class Painter void DrawArc(Vector, double, double, double); void DrawEllipse(Vector, double, double); void DrawHandle(Vector); + void DrawCross(Vector); + void DrawRectCorners(Rect); void DrawArrowHandle(Vector, double); void DrawArrowToLineHandle(Vector, double); void DrawLine(int, int, int, int); @@ -43,14 +44,9 @@ class Painter static Vector CartesianToQtCoords(Vector); static Vector QtToCartesianCoords(Vector); - public: - // Class variables -// static Vector origin; // The window origin, not location of the origin -// static double zoom; // Window zoom factor -// static Vector screenSize; // Width & height of the window we're drawing on - private: QPainter * painter; }; #endif // __PAINTER_H__ + diff --git a/src/rect.cpp b/src/rect.cpp new file mode 100644 index 0000000..8207f23 --- /dev/null +++ b/src/rect.cpp @@ -0,0 +1,100 @@ +// +// rect.cpp: Rectangle object implementation +// +// Part of the Architektonas Project +// (C) 2016 Underground Software +// See the README and GPLv3 files for licensing and warranty information +// +// JLH = James Hammons +// +// WHO WHEN WHAT +// --- ---------- ------------------------------------------------------------ +// JLH 11/10/2016 Created this file +// + +#include "rect.h" +//#include + + +Rect::Rect(): l(0), r(0), t(0), b(0) +{ +} + + +Rect::Rect(double ll, double rr, double tt, double bb): + l(ll), r(rr), t(tt), b(bb) +{ + Normalize(); +} + + +Rect::Rect(Point tl, Point br): l(tl.x), r(br.x), t(tl.y), b(br.y) +{ + Normalize(); +} + + +Rect & Rect::operator*=(double scale) +{ + l *= scale; + r *= scale; + t *= scale; + b *= scale; + return *this; +} + + +Rect & Rect::operator|=(Rect r2) +{ +//printf("operatore|=\nthis = (%lf, %lf, %lf, %lf), r = (%lf, %lf, %lf, %lf)\n", l, t, r, b, r2.l, r2.t, r2.r, r2.b); + if (r2.l < l) + l = r2.l; + + if (r2.r > r) + r = r2.r; + + if (r2.t > t) + t = r2.t; + + if (r2.b < b) + b = r2.b; + + return *this; +} + + +void Rect::Normalize(void) +{ + if (l > r) + { + double x = l; + l = r; + r = x; + } + + if (b > t) + { + double x = b; + b = t; + t = x; + } +} + + +void Rect::Translate(Point p) +{ + l += p.x; + r += p.x; + t += p.y; + b += p.y; +} + + +void Rect::Expand(double amt) +{ + l -= amt; + r += amt; + t += amt; + b -= amt; +} + diff --git a/src/rect.h b/src/rect.h new file mode 100644 index 0000000..0a8478a --- /dev/null +++ b/src/rect.h @@ -0,0 +1,26 @@ +#ifndef __RECT_H__ +#define __RECT_H__ + +// +// We're doing this because the Qt implementation is non-Cartesian compliant. +// Also, it auto-normalizes rects constructed using the constructors. :-) +// + +#include "vector.h" + +struct Rect +{ + double l, r, t, b; + + Rect(); + Rect(double ll, double rr, double tt, double bb); + Rect(Point tl, Point br); + Rect & operator*=(double scale); + Rect & operator|=(Rect x); + void Normalize(void); + void Translate(Point p); + void Expand(double amt); +}; + +#endif // __RECT_H__ + diff --git a/src/structs.h b/src/structs.h index 6920e49..bc68adf 100644 --- a/src/structs.h +++ b/src/structs.h @@ -7,7 +7,7 @@ #include "global.h" #include "vector.h" -enum ObjectType { OTNone, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTDimension, OTSpline, OTText, OTContainer }; +enum ObjectType { OTNone = 0, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTDimension, OTSpline, OTText, OTContainer, OTCount }; enum DimensionType { DTLinear, DTLinearVert, DTLinearHorz, DTRadial, DTDiametric, DTCircumferential, DTAngular, DTLeader }; @@ -106,14 +106,29 @@ struct Container { OBJECT_COMMON; std::vector objects; double scale; + bool topLevel; - Container(): type(OTContainer), id(Global::objectID++), selected(false), hovered(false), hitObject(false) {} + Container(bool tl = false): type(OTContainer), id(Global::objectID++), selected(false), hovered(false), hitObject(false), topLevel(tl) {} +// void DeleteContents(void) {} +/* void DeleteContents(Container * c) + { + std::vector::iterator i; + + for(i=c->objects.begin(); i!=c->objects.end(); i++) + { + Object * obj = (Object *)(*i); + + if (obj->type == OTContainer) + DeleteContainer((Container *)obj); + + delete *i; + } + }*/ }; struct Object { OBJECT_COMMON; }; - #endif // __STRUCTS_H__ diff --git a/src/utils.cpp b/src/utils.cpp index 12f7146..02d6b20 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -90,6 +90,25 @@ Object * CopyObject(Object * obj) } +void MoveSelectedObjectsTo(std::vector & dest, std::vector & from) +{ + std::vector::iterator i = from.begin(); + + while (i != from.end()) + { + Object * obj = (Object *)(*i); + + if (obj->selected) + { + dest.push_back(*i); + from.erase(i); + } + else + i++; + } +} + + void AddObjectsTo(std::vector & dest, std::vector & from) { for(std::vector::iterator i=from.begin(); i!=from.end(); i++) @@ -106,6 +125,38 @@ void ClearSelected(std::vector & v) } +void SelectAll(std::vector & v) +{ + std::vector::iterator i; + + for(i=v.begin(); i!=v.end(); i++) + ((Object *)(*i))->selected = true; +} + + +// +// Recursively go down thru the Container's vectors, deleting all the objects +// contained therein. Once that is done, the main Container can be deleted. We +// don't have to worry about the underlying std::vectors, as they have their +// own destructors--plus they don't take ownership of objects, which is why we +// have to keep track of that stuff ourselves. :-P Believe it or not, this is a +// Good Thing(TM). ;-) +// +void DeleteContents(std::vector & v) +{ + std::vector::iterator i; + + for(i=v.begin(); i!=v.end(); i++) + { + Object * obj = (Object *)(*i); + + if (obj->type == OTContainer) + DeleteContents(((Container *)obj)->objects); + + delete obj; + } +} + void DeleteSelectedObjects(std::vector & v) { std::vector::iterator i = v.begin(); @@ -125,6 +176,26 @@ void DeleteSelectedObjects(std::vector & v) } +// +// This is used to remove selected objects from one container in order to move +// them to a different container. +// +void RemoveSelectedObjects(std::vector & v) +{ + std::vector::iterator i = v.begin(); + + while (i != v.end()) + { + Object * obj = (Object *)(*i); + + if (obj->selected) + v.erase(i); + else + i++; + } +} + + void SavePointsFrom(std::vector & v, std::vector & save) { save.clear(); @@ -156,3 +227,21 @@ void RestorePointsTo(std::vector & v, std::vector & s) } +void TranslateObject(Object * obj, Point delta) +{ + if (obj->type == OTContainer) + { + Container * c = (Container *)obj; + std::vector::iterator i; + + for(i=c->objects.begin(); i!=c->objects.end(); i++) + TranslateObject((Object *)*i, delta); + } + else + { + obj->p[0] += delta; + obj->p[1] += delta; + } +} + + diff --git a/src/utils.h b/src/utils.h index 23b6ad3..24c0414 100644 --- a/src/utils.h +++ b/src/utils.h @@ -6,11 +6,16 @@ void CopyObjects(std::vector & from, std::vector & to); Object * CopyObject(Object * obj); +void MoveSelectedObjectsTo(std::vector & dest, std::vector & from); void AddObjectsTo(std::vector & dest, std::vector & from); void ClearSelected(std::vector & v); +void SelectAll(std::vector & v); +void DeleteContents(std::vector & v); void DeleteSelectedObjects(std::vector & v); +void RemoveSelectedObjects(std::vector & v); void SavePointsFrom(std::vector & v, std::vector & s); void RestorePointsTo(std::vector & v, std::vector & s); +void TranslateObject(Object * obj, Point delta); #endif // __UTILS_H__