From: Shamus Hammons Date: Thu, 23 Apr 2020 17:34:04 +0000 (-0500) Subject: Changes to make containers behave like a first-class object. X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?p=architektonas;a=commitdiff_plain;h=790c1a6d97f73f7457c7fad7e82fa29e5b6accd5 Changes to make containers behave like a first-class object. Also, added cut/copy/paste functionality, and ability to save layer information with object data. This change bumps the drawing version to 1.2; this required some infrastructure changes to the way layers are represented and handled. Also preliminary addition of Trim, Triangulate, and Parallel tools, and support for snapping to line midpoints. --- diff --git a/src/applicationwindow.cpp b/src/applicationwindow.cpp index 41e2f2c..7dd57ac 100644 --- a/src/applicationwindow.cpp +++ b/src/applicationwindow.cpp @@ -98,6 +98,7 @@ 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(this, SIGNAL(ReloadLayers()), lw, SLOT(Reload())); connect(drawing, SIGNAL(ObjectHovered(Object *)), ow, SLOT(ShowInfo(Object *))); } @@ -113,9 +114,38 @@ void ApplicationWindow::closeEvent(QCloseEvent * event) void ApplicationWindow::FileNew(void) { - // Should warn the user if drawing hasn't been saved... !!! FIX !!! + // Warn the user if drawing has changed and hasn't been saved... + if (drawing->dirty) + { + QMessageBox msg; + + msg.setText("The document has been modified."); + msg.setInformativeText("Do you want to save your changes?"); + msg.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + msg.setDefaultButton(QMessageBox::Save); + int response = msg.exec(); + + switch (response) + { + case QMessageBox::Save: + // Save was clicked + FileSave(); + break; + case QMessageBox::Discard: + // Don't Save was clicked + break; + case QMessageBox::Cancel: + // Cancel was clicked + return; +// break; + } + } + + FileIO::ResetLayerVectors(); + emit ReloadLayers(); DeleteContents(drawing->document.objects); drawing->document.objects.clear(); + drawing->dirty = false; drawing->update(); documentName.clear(); setWindowTitle("Architektonas - Untitled"); @@ -161,9 +191,12 @@ void ApplicationWindow::FileOpen(void) //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); + emit ReloadLayers(); // 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. + // Objects, and the Containers (non-empty) can be moved this way with no + // problem. drawing->document = container; + drawing->dirty = false; drawing->update(); documentName = filename; setWindowTitle(QString("Architektonas - %1").arg(documentName)); @@ -204,6 +237,7 @@ void ApplicationWindow::FileSave(void) return; } + drawing->dirty = false; setWindowTitle(QString("Architektonas - %1").arg(documentName)); statusBar()->showMessage(tr("Drawing saved.")); } @@ -222,6 +256,16 @@ void ApplicationWindow::FileSaveAs(void) } +void ApplicationWindow::contextMenuEvent(QContextMenuEvent * event) +{ + QMenu menu(this); + menu.addAction(mirrorAct); + menu.addAction(rotateAct); + menu.addAction(trimAct); + menu.exec(event->globalPos()); +} + + void ApplicationWindow::SnapToGridTool(void) { Global::snapToGrid = snapToGridAct->isChecked(); @@ -603,6 +647,11 @@ else ClearSelected(c->objects); c->selected = true; c->layer = Global::currentLayer; + + Rect r = drawing->GetObjectExtents((Object *)c); + c->p[0] = r.TopLeft(); + c->p[1] = r.BottomRight(); + drawing->select.clear(); drawing->select.push_back(c); statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(numSelected)); @@ -717,6 +766,9 @@ void ApplicationWindow::HandleGridSizeInBaseUnits(QString text) void ApplicationWindow::HandleDimensionSize(QString text) { +/* +This is the third input on the view toolbar; not sure what it was supposed to do... +*/ // Parse the text... bool ok; double value = text.toDouble(&ok); @@ -729,6 +781,39 @@ void ApplicationWindow::HandleDimensionSize(QString text) drawing->update(); } +void ApplicationWindow::EditCopy(void) +{ + if (drawing->select.size() > 0) + { + DeleteContents(clipboard); + CopySelectedObjectsTo(clipboard, drawing->document.objects); + } +} + + +void ApplicationWindow::EditCut(void) +{ + if (drawing->select.size() > 0) + { + DeleteContents(clipboard); + MoveSelectedObjectsTo(clipboard, drawing->document.objects); + drawing->update(); + } +} + + +void ApplicationWindow::EditPaste(void) +{ + if (clipboard.size() > 0) + { + // We want to maybe make it so that the pasted objects are being moved in a "mouse drag" state... + // This only moves the cut/copied from the clipboard to the drawing. +// AddObjectsTo(drawing->document.objects, clipboard); + CopyObjects(clipboard, drawing->document.objects); + drawing->update(); + } +} + void ApplicationWindow::CreateActions(void) { @@ -815,6 +900,14 @@ void ApplicationWindow::CreateActions(void) triangulateAct = CreateAction(tr("&Triangulate"), tr("Triangulate"), tr("Make triangles from selected lines, preserving their lengths."), QIcon(":/res/triangulate-tool.png"), QKeySequence("t,g"), true); connect(triangulateAct, SIGNAL(triggered()), this, SLOT(TriangulateTool())); + editCutAct = CreateAction(tr("&Cut Objects"), tr("Cut Objects"), tr("Cut objects from the drawing to the clipboard."), QIcon(":/res/editcut2.png"), QKeySequence(tr("Ctrl+x"))); + connect(editCutAct, SIGNAL(triggered()), this, SLOT(EditCut())); + + editCopyAct = CreateAction(tr("&Copy Objects"), tr("Copy Objects"), tr("Copy objects from the drawing to the clipboard."), QIcon(":/res/editcopy2.png"), QKeySequence(tr("Ctrl+c"))); + connect(editCopyAct, SIGNAL(triggered()), this, SLOT(EditCopy())); + + editPasteAct = CreateAction(tr("&Paste Objects"), tr("Paste Objects"), tr("Paste objects from the clipboard to the drawing."), QIcon(":/res/editpaste2.png"), QKeySequence(tr("Ctrl+v"))); + connect(editPasteAct, SIGNAL(triggered()), this, SLOT(EditPaste())); //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. @@ -830,8 +923,8 @@ void ApplicationWindow::CreateActions(void) // // Consolidates action creation from a multi-step process to a single-step one. // -QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip, - QIcon icon, QKeySequence key, bool checkable/*= false*/) +QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, + QString statustip, QIcon icon, QKeySequence key, bool checkable/*= false*/) { QAction * action = new QAction(icon, name, this); action->setToolTip(tooltip); @@ -847,8 +940,9 @@ QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString // This is essentially the same as the previous function, but this allows more // than one key sequence to be added as key shortcuts. // -QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip, - QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/) +QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, + QString statustip, QIcon icon, QKeySequence key1, QKeySequence key2, + bool checkable/*= false*/) { QAction * action = new QAction(icon, name, this); action->setToolTip(tooltip); @@ -890,6 +984,9 @@ void ApplicationWindow::CreateMenus(void) menu->addAction(connectAct); menu->addAction(disconnectAct); menu->addSeparator(); + menu->addAction(editCutAct); + menu->addAction(editCopyAct); + menu->addAction(editPasteAct); menu->addAction(deleteAct); menu->addSeparator(); menu->addAction(addLineAct); @@ -938,6 +1035,9 @@ void ApplicationWindow::CreateToolbars(void) toolbar->addAction(mirrorAct); toolbar->addAction(trimAct); toolbar->addAction(triangulateAct); + toolbar->addAction(editCutAct); + toolbar->addAction(editCopyAct); + toolbar->addAction(editPasteAct); toolbar->addAction(deleteAct); toolbar->addAction(connectAct); toolbar->addAction(disconnectAct); diff --git a/src/applicationwindow.h b/src/applicationwindow.h index 32ac8f6..d10e4dd 100644 --- a/src/applicationwindow.h +++ b/src/applicationwindow.h @@ -19,6 +19,7 @@ class ApplicationWindow: public QMainWindow protected: void closeEvent(QCloseEvent * event); + void contextMenuEvent(QContextMenuEvent * event); private slots: void FileNew(void); @@ -49,6 +50,12 @@ class ApplicationWindow: public QMainWindow void HandleGridSizeInPixels(int); void HandleGridSizeInBaseUnits(QString); void HandleDimensionSize(QString); + void EditCut(void); + void EditCopy(void); + void EditPaste(void); + + signals: + void ReloadLayers(void); private: void ClearUIToolStatesExcept(QAction *); @@ -98,6 +105,11 @@ class ApplicationWindow: public QMainWindow QAction * mirrorAct; QAction * trimAct; QAction * triangulateAct; + QAction * editCutAct; + QAction * editCopyAct; + QAction * editPasteAct; + + std::vector clipboard; // Class variables public: diff --git a/src/drawingview.cpp b/src/drawingview.cpp index f50fca5..60f941e 100644 --- a/src/drawingview.cpp +++ b/src/drawingview.cpp @@ -1,3 +1,4 @@ +// // drawingview.cpp // // Part of the Architektonas Project @@ -16,11 +17,14 @@ // // - Redo rendering code to *not* use Qt's transform functions, as they are tied // to a left-handed system and we need a right-handed one. [DONE] +// - Fixed length tool doesn't work on lines [DONE] // // STILL TO BE DONE: // // - Lots of stuff // - Layer locking (hiding works) +// - Fixed angle tool doesn't work on lines +// - Make it so "dirty" flag reflects drawing state // // Uncomment this for debugging... @@ -48,50 +52,27 @@ DrawingView::DrawingView(QWidget * parent/*= NULL*/): QWidget(parent), ctrlDown(false), altDown(false), gridBackground(BACKGROUND_MAX_SIZE, BACKGROUND_MAX_SIZE), scale(1.0), offsetX(-10), offsetY(-10), document(true), - gridPixels(0), collided(false), hoveringIntersection(false), - dragged(NULL), draggingObject(false), angleSnap(false) + gridPixels(0), collided(false), scrollDrag(false), hoverPointValid(false), + hoveringIntersection(false), dragged(NULL), draggingObject(false), + angleSnap(false), dirty(false) { //wtf? doesn't work except in c++11??? document = { 0 }; setBackgroundRole(QPalette::Base); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); Global::gridSpacing = 12.0; // In base units (inch is default) -#if 0 - Line * line = new Line(Vector(5, 5), Vector(50, 40), &document); + + Line * line = new Line(Vector(5, 5), Vector(50, 40), 2.0, 0xFF7F00, LSDash); document.Add(line); - document.Add(new Line(Vector(50, 40), Vector(10, 83), &document)); - document.Add(new Line(Vector(10, 83), Vector(17, 2), &document)); - document.Add(new Circle(Vector(100, 100), 36, &document)); - document.Add(new Circle(Vector(50, 150), 49, &document)); - document.Add(new Arc(Vector(300, 300), 32, PI / 4.0, PI * 1.3, &document)), - document.Add(new Arc(Vector(200, 200), 60, PI / 2.0, PI * 1.5, &document)); -#if 1 - Dimension * dimension = new Dimension(Vector(0, 0), Vector(0, 0), DTLinear, &document); - line->SetDimensionOnLine(dimension); - document.Add(dimension); -#else - // Alternate way to do the above... - line->SetDimensionOnLine(); -#endif -#else - Line * line = new Line;//(Vector(5, 5), Vector(50, 40), &document); - line->p[0] = Vector(5, 5); - line->p[1] = Vector(50, 40); - line->type = OTLine; - line->thickness = 2.0; - line->style = LSDash; - line->color = 0xFF7F00; - line->layer = 0; - document.objects.push_back(line); - document.objects.push_back(new Line(Vector(50, 40), Vector(10, 83))); - document.objects.push_back(new Line(Vector(10, 83), Vector(17, 2))); - document.objects.push_back(new Circle(Vector(100, 100), 36)); - document.objects.push_back(new Circle(Vector(50, 150), 49)); - document.objects.push_back(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)), - document.objects.push_back(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75)); - document.objects.push_back(new Dimension(Vector(50, 40), Vector(5, 5))); - document.objects.push_back(new Text(Vector(10, 83), "Here is some awesome text!")); -#endif + document.Add(new Line(Vector(50, 40), Vector(10, 83))); + document.Add(new Line(Vector(10, 83), Vector(17, 2))); + document.Add(new Circle(Vector(100, 100), 36)); + document.Add(new Circle(Vector(50, 150), 49)); + document.Add(new Arc(Vector(300, 300), 32, TAU / 8.0, TAU * 0.65)), + document.Add(new Arc(Vector(200, 200), 60, TAU / 4.0, TAU * 0.75)); + document.Add(new Text(Vector(10, 83), "Here is some awesome text!")); + + AddDimensionTo(line); /* Here we set the grid size in pixels--12 in this case. Initially, we have our @@ -365,6 +346,18 @@ QPoint DrawingView::GetAdjustedClientPosition(int x, int y) } +void DrawingView::focusOutEvent(QFocusEvent * /*event*/) +{ +// printf("DrawingView::focusOutEvent()...\n"); + // Make sure all modkeys being held are marked as released when the app + // loses focus (N.B.: This only works because the app sets the focus policy + // of this object to something other than Qt::NoFocus) + shiftDown = ctrlDown = altDown = false; + scrollDrag = false; + setCursor(Qt::ArrowCursor); +} + + void DrawingView::paintEvent(QPaintEvent * /*event*/) { QPainter qtPainter(this); @@ -406,6 +399,9 @@ void DrawingView::paintEvent(QPaintEvent * /*event*/) if (hoveringIntersection) painter.DrawHandle(intersectionPoint); + if (hoverPointValid) + painter.DrawHandle(hoverPoint); + if (!informativeText.isEmpty()) painter.DrawInformativeText(informativeText); } @@ -414,6 +410,12 @@ void DrawingView::paintEvent(QPaintEvent * /*event*/) // // Renders objects in the passed in vector // +/* +N.B.: Since we have "hoverPointValid" drawing regular object handles above, + we can probably do away with a lot of them that are being done down below. + !!! FIX !!! + [Well, it seems to work OK *except* when you move one of the points, then you get to see nothing. Is it worth fixing there to get rid of problems here? Have to investigate...] +*/ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int layer, bool ignoreLayer/*= false*/) { std::vector::iterator i; @@ -451,7 +453,11 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int if (obj->hitPoint[1]) painter->DrawHandle(obj->p[1]); + if (obj->hitObject) + painter->DrawSmallHandle(Geometry::Midpoint((Line *)obj)); + break; + case OTCircle: painter->SetBrush(QBrush(Qt::NoBrush)); painter->DrawEllipse(obj->p[0], obj->radius[0], obj->radius[0]); @@ -460,6 +466,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int painter->DrawHandle(obj->p[0]); break; + case OTArc: painter->DrawArc(obj->p[0], obj->radius[0], obj->angle[0], obj->angle[1]); @@ -473,6 +480,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int painter->DrawHandle(obj->p[0] + (Vector(cos(obj->angle[0] + obj->angle[1]), sin(obj->angle[0] + obj->angle[1])) * obj->radius[0])); break; + case OTDimension: { Dimension * d = (Dimension *)obj; @@ -640,6 +648,7 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int break; } + case OTText: { Text * t = (Text *)obj; @@ -653,30 +662,36 @@ void DrawingView::RenderObjects(Painter * painter, std::vector & v, int painter->DrawTextObject(t->p[0], t->s.c_str(), scaledThickness, t->angle[0]); break; } + case OTSpline: { break; } + case OTPolygon: { break; } + case OTContainer: { // Containers require recursive rendering... Container * c = (Container *)obj; - RenderObjects(painter, (*c).objects, layer); +printf("About to render container: # objs=%i, layer=%i\n", (*c).objects.size(), layer); + RenderObjects(painter, (*c).objects, layer, ignoreLayer); //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); +// Rect r = GetObjectExtents(obj); +// painter->DrawRectCorners(r); + painter->DrawRectCorners(Rect(c->p[0], c->p[1])); } break; } + default: break; } @@ -709,6 +724,7 @@ void DrawingView::GetSelection(std::vector & v) } +#if 0 void DrawingView::GetHovered(std::vector & v) { v.clear(); @@ -723,6 +739,25 @@ void DrawingView::GetHovered(std::vector & v) // } } } +#endif + + +std::vector DrawingView::GetHovered(void) +{ + std::vector v; + std::vector::iterator i; + + for(i=document.objects.begin(); i!=document.objects.end(); i++) + { + if (((Object *)(*i))->hovered) +// { +//printf("GetHovered: adding object (%X) to hover... hp1=%s, hp2=%s, hl=%s\n", (*i), (((Line *)(*i))->hitPoint[0] ? "true" : "false"), (((Line *)(*i))->hitPoint[1] ? "true" : "false"), (((Line *)(*i))->hitObject ? "true" : "false")); + v.push_back(*i); +// } + } + + return v; +} void DrawingView::resizeEvent(QResizeEvent * /*event*/) @@ -749,6 +784,12 @@ void DrawingView::ToolHandler(int mode, Point p) MirrorHandler(mode, p); else if (Global::tool == TTDimension) DimensionHandler(mode, p); + else if (Global::tool == TTTriangulate) + TriangulateHandler(mode, p); + else if (Global::tool == TTTrim) + TrimHandler(mode, p); + else if (Global::tool == TTParallel) + ParallelHandler(mode, p); } @@ -923,6 +964,7 @@ void DrawingView::LineHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseMove: if (Global::toolState == TSNone) toolPoint[0] = p; @@ -930,6 +972,7 @@ void DrawingView::LineHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseUp: if (Global::toolState == TSNone) { @@ -965,6 +1008,7 @@ void DrawingView::CircleHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseMove: if (Global::toolState == TSNone) toolPoint[0] = p; @@ -972,6 +1016,7 @@ void DrawingView::CircleHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseUp: if (Global::toolState == TSNone) { @@ -1013,6 +1058,7 @@ void DrawingView::ArcHandler(int mode, Point p) toolPoint[3] = p; break; + case ToolMouseMove: if (Global::toolState == TSNone) toolPoint[0] = p; @@ -1030,6 +1076,7 @@ void DrawingView::ArcHandler(int mode, Point p) } break; + case ToolMouseUp: if (Global::toolState == TSNone) { @@ -1082,7 +1129,8 @@ void DrawingView::RotateHandler(int mode, Point p) if (Global::toolState == TSNone) { toolPoint[0] = p; - SavePointsFrom(select, toolScratch); +// SavePointsFrom(select, toolScratch); + CopyObjects(select, toolScratch2); Global::toolState = TSPoint1; } else if (Global::toolState == TSPoint1) @@ -1091,6 +1139,7 @@ void DrawingView::RotateHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseMove: if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone)) toolPoint[0] = p; @@ -1104,28 +1153,69 @@ void DrawingView::RotateHandler(int mode, Point p) angleSnap = true; double angle = Vector(toolPoint[0], toolPoint[1]).Angle(); std::vector::iterator j = select.begin(); - std::vector::iterator i = toolScratch.begin(); +// std::vector::iterator i = toolScratch.begin(); + std::vector::iterator i = toolScratch2.begin(); - for(; i!=toolScratch.end(); i++, j++) +// for(; i!=toolScratch.end(); i++, j++) + for(; i!=toolScratch2.end(); i++, j++) { - Object obj = *i; - Point p1 = Geometry::RotatePointAroundPoint(obj.p[0], toolPoint[0], angle); - Point p2 = Geometry::RotatePointAroundPoint(obj.p[1], toolPoint[0], angle); - Object * obj2 = (Object *)(*j); - obj2->p[0] = p1; - obj2->p[1] = p2; +// Object objT = *i; +// Point p1 = Geometry::RotatePointAroundPoint(objT.p[0], toolPoint[0], angle); +// Point p2 = Geometry::RotatePointAroundPoint(objT.p[1], toolPoint[0], angle); + Object * objT = (Object *)(*i); + Object * objS = (Object *)(*j); - if (obj.type == OTArc) + Point p1 = Geometry::RotatePointAroundPoint(objT->p[0], toolPoint[0], angle); + Point p2 = Geometry::RotatePointAroundPoint(objT->p[1], toolPoint[0], angle); + + objS->p[0] = p1; + objS->p[1] = p2; + +// if (objT.type == OTArc || objT.type == OTText) + if (objT->type == OTArc || objT->type == OTText) { - obj2->angle[0] = obj.angle[0] + angle; +// objS->angle[0] = objT.angle[0] + angle; + objS->angle[0] = objT->angle[0] + angle; - if (obj2->angle[0] > TAU) - obj2->angle[0] -= TAU; + if (objS->angle[0] > TAU) + objS->angle[0] -= TAU; + } +// else if (objT.type == OTContainer) + else if (objT->type == OTContainer) + { + // OK, this doesn't work because toolScratch only has points and nothing in the containers... [ACTUALLY... toolScratch is is a vector of type Object... which DOESN'T have an objects vector in it...] +// Container * c = (Container *)&objT; + Container * c = (Container *)objT; + Container * c2 = (Container *)objS; + std::vector::iterator l = c->objects.begin(); + // TODO: Rotate items in the container + // TODO: Make this recursive + for(std::vector::iterator k=c2->objects.begin(); k!=c2->objects.end(); k++, l++) + { + Object * obj3 = (Object *)(*k); + Object * obj4 = (Object *)(*l); + + p1 = Geometry::RotatePointAroundPoint(obj4->p[0], toolPoint[0], angle); + p2 = Geometry::RotatePointAroundPoint(obj4->p[1], toolPoint[0], angle); + + obj3->p[0] = p1; + obj3->p[1] = p2; +// obj3->angle[0] = objT.angle[0] + angle; + obj3->angle[0] = obj4->angle[0] + angle; + + if (obj3->angle[0] > TAU) + obj3->angle[0] -= TAU; + } + + Rect r = GetObjectExtents(objS); + c2->p[0] = r.TopLeft(); + c2->p[1] = r.BottomRight(); } } } break; + case ToolMouseUp: if (Global::toolState == TSPoint1) { @@ -1149,30 +1239,39 @@ void DrawingView::RotateHandler(int mode, Point p) CopyObjects(select, temp); ClearSelected(temp); AddObjectsTo(document.objects, temp); - RestorePointsTo(select, toolScratch); +// RestorePointsTo(select, toolScratch); + RestorePointsTo(select, toolScratch2); return; } toolPoint[0] = p; Global::toolState = TSPoint1; - SavePointsFrom(select, toolScratch); +// SavePointsFrom(select, toolScratch); + DeleteContents(toolScratch2); + CopyObjects(select, toolScratch2); } break; + case ToolKeyDown: // Reset the selection if shift held down... if (shiftDown) - RestorePointsTo(select, toolScratch); +// RestorePointsTo(select, toolScratch); + RestorePointsTo(select, toolScratch2); break; + case ToolKeyUp: // Reset selection when key is let up if (!shiftDown) RotateHandler(ToolMouseMove, toolPoint[1]); break; + case ToolCleanup: - RestorePointsTo(select, toolScratch); +// RestorePointsTo(select, toolScratch); + RestorePointsTo(select, toolScratch2); + DeleteContents(toolScratch2); } } @@ -1194,6 +1293,7 @@ void DrawingView::MirrorHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseMove: if ((Global::toolState == TSPoint1) || (Global::toolState == TSNone)) toolPoint[0] = p; @@ -1218,6 +1318,11 @@ void DrawingView::MirrorHandler(int mode, Point p) obj2->p[0] = p1; obj2->p[1] = p2; +/* +N.B.: When mirroring an arc thru a horizontal axis, this causes the arc to have + a negative start angle which makes it impossible to interact with. + !!! FIX !!! +*/ if (obj.type == OTArc) { // This is 2*mirror angle - obj angle - obj span @@ -1230,6 +1335,7 @@ void DrawingView::MirrorHandler(int mode, Point p) } break; + case ToolMouseUp: if (Global::toolState == TSPoint1) { @@ -1263,18 +1369,21 @@ void DrawingView::MirrorHandler(int mode, Point p) } break; + case ToolKeyDown: // Reset the selection if shift held down... if (shiftDown) RestorePointsTo(select, toolScratch); break; + case ToolKeyUp: // Reset selection when key is let up if (!shiftDown) MirrorHandler(ToolMouseMove, toolPoint[1]); break; + case ToolCleanup: RestorePointsTo(select, toolScratch); } @@ -1292,6 +1401,7 @@ void DrawingView::DimensionHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseMove: if (Global::toolState == TSNone) toolPoint[0] = p; @@ -1299,6 +1409,7 @@ void DrawingView::DimensionHandler(int mode, Point p) toolPoint[1] = p; break; + case ToolMouseUp: if (Global::toolState == TSNone) { @@ -1323,10 +1434,265 @@ void DrawingView::DimensionHandler(int mode, Point p) } +void DrawingView::TriangulateHandler(int mode, Point/*p*/) +{ + switch (mode) + { + case ToolMouseDown: + { + // Skip if nothing hovered... + if (numHovered != 1) + break; + + std::vector hover = GetHovered(); + Object * obj = (Object *)hover[0]; + + // Skip if it's not a line... + if (obj->type != OTLine) + break; + + if (Global::toolState == TSNone) + toolObj[0] = obj; + else if (Global::toolState == TSPoint2) + toolObj[1] = obj; + else + toolObj[2] = obj; + + break; + } +#if 0 + case ToolMouseMove: + if (Global::toolState == TSNone) + toolPoint[0] = p; + else if (Global::toolState == TSPoint2) + toolPoint[1] = p; + else if (Global::toolState == TSPoint3) + { + toolPoint[2] = p; + angleSnap = true; + } + else + { + toolPoint[3] = p; + angleSnap = true; + } + + break; +#endif + case ToolMouseUp: + if (Global::toolState == TSNone) + { + Global::toolState = TSPoint2; + } + else if (Global::toolState == TSPoint2) + { +/* if (shiftDown) + { + // Key override is telling us to start arc at new center, not + // continue the current one. + toolPoint[0] = toolPoint[1]; + return; + }*/ + + Global::toolState = TSPoint3; + } + else + { + double len2 = Vector::Magnitude(toolObj[1]->p[0], toolObj[1]->p[1]); + double len3 = Vector::Magnitude(toolObj[2]->p[0], toolObj[2]->p[1]); + + Circle c1(toolObj[0]->p[0], len2); + Circle c2(toolObj[0]->p[1], len3); + + Geometry::CheckCircleToCircleIntersection((Object *)&c1, (Object *)&c2); + + // Only move lines if the triangle formed by them is not degenerate + if (Global::numIntersectPoints > 0) + { + toolObj[1]->p[0] = toolObj[0]->p[0]; + toolObj[1]->p[1] = Global::intersectPoint[0]; + + toolObj[2]->p[0] = Global::intersectPoint[0]; + toolObj[2]->p[1] = toolObj[0]->p[1]; + } + + Global::toolState = TSNone; + } + } +} + + +void DrawingView::TrimHandler(int mode, Point p) +{ +/* +n.b.: this code is lifted straight out of the old oo code. needs to be updated. +*/ + switch (mode) + { + case ToolMouseDown: + { +#if 0 + Object * toTrim = doc->lastObjectHovered; + + if (toTrim == NULL) + return; + + Vector v(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint); + + // Check to see which case we have... + // We're trimming point #1... + if (t == 0) + { + ((Line *)toTrim)->position = ((Line *)toTrim)->position + (v * u); + } + else if (u == 1.0) + { + ((Line *)toTrim)->endpoint = ((Line *)toTrim)->position + (v * t); + } + else + { + Point p1 = ((Line *)toTrim)->position + (v * t); + Point p2 = ((Line *)toTrim)->position + (v * u); + Point p3 = ((Line *)toTrim)->endpoint; + ((Line *)toTrim)->endpoint = p1; + Line * line = new Line(p2, p3); + emit ObjectReady(line); + } + + doc->lastObjectHovered = NULL; +#endif + } + break; + + case ToolMouseMove: + { +#if 0 + Object * toTrim = doc->lastObjectHovered; + t = 0, u = 1.0; + + if (toTrim == NULL) + return; + + if (toTrim->type != OTLine) + return; + + double pointHoveredT = Geometry::ParameterOfLineAndPoint(((Line *)toTrim)->position, ((Line *)toTrim)->endpoint, point); + + std::vector::iterator i; + + for(i=doc->objects.begin(); i!=doc->objects.end(); i++) + { + // Can't trim against yourself... :-P + if (*i == toTrim) + continue; + + Object * trimAgainst = *i; + double t1;//, u1; + + if ((toTrim->type != OTLine) || (trimAgainst->type != OTLine)) + continue; + + int intersects = Geometry::Intersects((Line *)toTrim, (Line *)trimAgainst, &t1);//, &u1); + + if (intersects) + { + // Now what? We don't know which side to trim! + // ... now we do, we know which side of the Line we're on! + if ((t1 > t) && (t1 < pointHoveredT)) + t = t1; + + if ((t1 < u) && (t1 > pointHoveredT)) + u = t1; + } + } +#endif + // Bail out if nothing hovered... + if (numHovered != 1) + { + toolObj[0] = NULL; + return; + } + + std::vector hover = GetHovered(); + Object * obj = (Object *)hover[0]; + + // Skip if it's not a line... + if (obj->type != OTLine) + { + toolObj[0] = NULL; + return; + } + + toolObj[0] = obj; + double hoveredParam = Geometry::ParameterOfLineAndPoint(obj->p[0], obj->p[1], p); + + // Currently only deal with line against line trimming, can expand to + // others as well (line/circle, circle/circle, line/arc, etc) + std::vector::iterator i; + for(i=document.objects.begin(); i!=document.objects.end(); i++) + { + obj = (Object *)(*i); + + if (obj == toolObj[0]) + continue; + else if (obj->type != OTLine) + continue; + + Geometry::CheckLineToLineIntersection(toolObj[0], obj); + + if (Global::numIntersectParams > 0) + { + // Mark the line segment somehow (the side the mouse is on) so that it can be drawn & trimmed when we hit ToolMouseDown. + } + } + } + break; + + case ToolMouseUp: + break; + + case ToolKeyDown: + break; + + case ToolKeyUp: + break; + + case ToolCleanup: + break; + } +} + + +void DrawingView::ParallelHandler(int mode, Point /*p*/) +{ + switch (mode) + { + case ToolMouseDown: + break; + + case ToolMouseMove: + break; + + case ToolMouseUp: + break; + + case ToolKeyDown: + break; + + case ToolKeyUp: + break; + + case ToolCleanup: + break; + } +} + + void DrawingView::mousePressEvent(QMouseEvent * event) { if (event->button() == Qt::LeftButton) { +printf("mousePressEvent::Qt::LeftButton numHovered=%li\n", numHovered); Vector point = Painter::QtToCartesianCoords(Vector(event->x(), event->y())); // Handle tool processing, if any @@ -1334,6 +1700,8 @@ void DrawingView::mousePressEvent(QMouseEvent * event) { if (hoveringIntersection) point = intersectionPoint; + else if (hoverPointValid) + point = hoverPoint; else if (Global::snapToGrid) point = SnapPointToGrid(point); @@ -1358,12 +1726,14 @@ void DrawingView::mousePressEvent(QMouseEvent * event) { AddHoveredToSelection(); update(); // needed?? - GetHovered(hover); // prolly needed - dragged = (Object *)hover[0]; +// GetHovered(hover); // prolly needed + std::vector hover2 = GetHovered(); + dragged = (Object *)hover2[0]; draggingObject = true; +printf("mousePressEvent::numHovered > 0 (hover2[0]=$%llx, type=%s)\n", dragged, objName[dragged->type]); // Alert the pen widget - emit(ObjectSelected(dragged)); + emit ObjectSelected(dragged); // See if anything is using just a straight click on a handle if (HandleObjectClicked()) @@ -1377,9 +1747,26 @@ void DrawingView::mousePressEvent(QMouseEvent * event) // We do it *after*... why? (doesn't seem to confer any advantage...) if (hoveringIntersection) oldPoint = intersectionPoint; + else if (hoverPointValid) + oldPoint = hoverPoint; else if (Global::snapToGrid) oldPoint = SnapPointToGrid(point); + // Needed for fixed length handling + if (Global::fixedLength) + { + if (dragged->type == OTLine) + { + dragged->length = Vector::Magnitude(dragged->p[0], dragged->p[1]); + } + } + + if (dragged->type == OTCircle) + { + // Save for informative text, uh, er, informing + dragged->length = dragged->radius[0]; + } + return; } @@ -1436,13 +1823,23 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) // Do object hit testing... bool needUpdate = HitTestObjects(point); - GetHovered(hover); +// GetHovered(hover); + std::vector hover2 = GetHovered(); +{ +if (needUpdate) +{ + printf("mouseMoveEvent:: numHovered=%li, hover2.size()=%li\n", numHovered, hover2.size()); + + if (hover2.size() > 0) + printf(" (hover2[0]=$%llX, type=%s)\n", hover2[0], objName[((Object *)hover2[0])->type]); +} +} // Check for multi-hover... if (numHovered > 1) { //need to check for case where hover is over 2 circles and a 3rd's center... - Object * obj1 = (Object *)hover[0], * obj2 = (Object *)hover[1]; + Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1]; Geometry::Intersects(obj1, obj2); int numIntersecting = Global::numIntersectParams; @@ -1480,6 +1877,29 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event) intersectionPoint = v1; } } + else if (numHovered == 1) + { + Object * obj = (Object *)hover2[0]; + + if (obj->type == OTLine) + { +/* +Not sure that this is the best way to handle this, but it works(TM)... +Except when lines are overlapping, then it doesn't work... !!! FIX !!! +*/ + Point midpoint = Geometry::Midpoint((Line *)obj); + Vector v1 = Vector::Magnitude(midpoint, point); + + if ((v1.Magnitude() * Global::zoom) < 8.0) + { + QString text = tr("Midpoint <%1, %2>"); + informativeText = text.arg(midpoint.x).arg(midpoint.y); + hoverPointValid = true; + hoverPoint = midpoint; + needUpdate = true; + } + } + } // Handle object movement (left button down & over an object) if ((event->buttons() & Qt::LeftButton) && draggingObject && !Global::tool) @@ -1549,7 +1969,7 @@ void DrawingView::mouseReleaseEvent(QMouseEvent * event) // Should we be doing this automagically? Hmm... // Clear our vectors select.clear(); - hover.clear(); +// hover.clear(); // Scoop 'em up std::vector::iterator i; @@ -1624,7 +2044,6 @@ void DrawingView::keyPressEvent(QKeyEvent * event) { scrollDrag = true; setCursor(Qt::SizeAllCursor); -// oldPoint = Vector(); oldPoint = oldScrollPoint; } @@ -1869,7 +2288,8 @@ void DrawingView::CheckObjectBounds(void) bounds = bounds.normalized(); #endif - // If the end of the arc is before the beginning, add 360 degrees to it + // If the end of the arc is before the beginning, add 360 degrees + // to it if (end < start) end += TAU; @@ -1916,9 +2336,19 @@ void DrawingView::CheckObjectBounds(void) break; } - default: + case OTContainer: + { + Container * c = (Container *)obj; + + if (Global::selection.contains(c->p[0].x, c->p[0].y) && Global::selection.contains(c->p[1].x, c->p[1].y)) + c->selected = true; + break; } + +// default: +// break; + } } } @@ -1938,14 +2368,14 @@ bool DrawingView::HitTestObjects(Point point) if (draggingObject && (obj == dragged)) continue; - if (HitTest(obj, point)) + if (HitTest(obj, point) == true) needUpdate = true; if (obj->hovered) { numHovered++; //printf("MouseMove: OBJECT HOVERED (numHovered = %i)\n", numHovered); - emit(ObjectHovered(obj)); + emit ObjectHovered(obj); } } @@ -2121,7 +2551,6 @@ bool DrawingView::HitTest(Object * obj, Point point) else if ((hCS2Point.Magnitude() * Global::zoom) < 8.0) obj->hitPoint[4] = true; -// return (hitPoint1 || hitPoint2 || hitLine || hitFlipSwitch || hitChangeSwitch1 || hitChangeSwitch2 ? true : false); obj->hovered = (obj->hitPoint[0] || obj->hitPoint[1] || obj->hitPoint[2] || obj->hitPoint[3] || obj->hitPoint[4] || obj->hitObject ? true : false); if ((oldHP0 != obj->hitPoint[0]) || (oldHP1 != obj->hitPoint[1]) || (oldHP2 != obj->hitPoint[2]) || (oldHP3 != obj->hitPoint[3]) || (oldHP4 != obj->hitPoint[4]) || (oldHO != obj->hitObject)) @@ -2152,24 +2581,70 @@ bool DrawingView::HitTest(Object * obj, Point point) case OTContainer: { - // Containers must be recursively tested... + // Containers must be recursively tested... Or do they??? +/* +So the idea here is to flatten the structure, *then* test the objects within. Flattening includes the Containers as well; we can do this because it's pointers all the way down. +*/ +// bool oldHitObj = c->hitObject, oldHovered = c->hovered; +// Object * oldClicked = c->clicked; +/* +still need to compare old state to new state, and set things up based upon that... +likely we can just rely on the object itself and steal its state like we have in the commented out portion below; can prolly rewrite the HitTest() portion to be one line: needUpdate = HitTest(cObj, point); +Well, you could if there was only one object in the Container. But since there isn't, we have to keep the if HitTest() == true then needUpdate = true bit. Because otherwise, a false result anywhere will kill the needed update elsewhere. +*/ + Container * c = (Container *)obj; c->hitObject = false; c->hovered = false; - std::vector::iterator i; + c->clicked = NULL; - for(i=c->objects.begin(); i!=c->objects.end(); i++) + std::vector flat = Flatten(c); + +//printf("HitTest::OTContainer (size=%li)\n", flat.size()); + for(std::vector::iterator i=flat.begin(); i!=flat.end(); i++) { - Object * cObj = (Object *)*i; + Object * cObj = (Object *)(*i); + + // Skip the flattened containers (if any)... + if (cObj->type == OTContainer) + continue; - if (HitTest(cObj, point)) + // We do it this way instead of needUpdate = HitTest() because we + // are checking more than one object, and that way of doing will + // not return consistent results. + if (HitTest(cObj, point) == true) +// { +//printf("HitTest::OTContainer, subobj ($%llX) hit!\n", cObj); needUpdate = true; +// c->hitObject = true; +// c->clicked = cObj; +// c->hovered = true; +// } + // Same reasons for doing it this way here apply. if (cObj->hitObject == true) + { +//printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj); c->hitObject = true; + c->clicked = cObj; + }//*/ + + if (cObj->hitPoint[0] == true) + { +//printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj); + c->hitPoint[0] = true; + c->clicked = cObj; + }//*/ + + if (cObj->hitPoint[1] == true) + { +//printf("HitTest::cObj->hitObject == true! ($%llX)\n", cObj); + c->hitPoint[1] = true; + c->clicked = cObj; + }//*/ if (cObj->hovered == true) - c->hovered = true; + c->hovered = true;//*/ } break; @@ -2240,9 +2715,27 @@ void DrawingView::HandleObjectMovement(Point point) { case OTLine: if (obj->hitPoint[0]) + { + if (Global::fixedLength) + { + Vector line = point - obj->p[1]; + Vector unit = line.Unit(); + point = obj->p[1] + (unit * obj->length); + } + obj->p[0] = point; + } else if (obj->hitPoint[1]) + { + if (Global::fixedLength) + { + Vector line = point - obj->p[0]; + Vector unit = line.Unit(); + point = obj->p[0] + (unit * obj->length); + } + obj->p[1] = point; + } else if (obj->hitObject) { obj->p[0] += delta; @@ -2256,12 +2749,11 @@ void DrawingView::HandleObjectMovement(Point point) obj->p[0] = point; else if (obj->hitObject) { -//this doesn't work. we need to save this on mouse down for this to work correctly! -// double oldRadius = obj->radius[0]; + double oldRadius = obj->length; obj->radius[0] = Vector::Magnitude(obj->p[0], point); - QString text = QObject::tr("Radius: %1");//\nScale: %2%"); - informativeText = text.arg(obj->radius[0], 0, 'd', 4);//.arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0); + QString text = QObject::tr("Radius: %1\nScale: %2%"); + informativeText = text.arg(obj->radius[0], 0, 'd', 4).arg(obj->radius[0] / oldRadius * 100.0, 0, 'd', 0); } break; @@ -2357,7 +2849,11 @@ void DrawingView::HandleObjectMovement(Point point) case OTContainer: // This is shitty, but works for now until I can code up something // nicer :-) - TranslateObject(obj, delta); +/* +The idea is to make it so whichever point on the object in question is being dragged becomes the snap point for the container; shouldn't be too difficult to figure out how to do this. +*/ +// TranslateObject(obj, delta); + TranslateContainer((Container *)obj, point, delta); break; default: @@ -2365,3 +2861,24 @@ void DrawingView::HandleObjectMovement(Point point) } } + +void DrawingView::AddDimensionTo(void * o) +{ + Object * obj = (Object *)o; + + switch (obj->type) + { + case OTLine: + document.Add(new Dimension(obj->p[0], obj->p[1])); + break; + case OTCircle: + break; + case OTEllipse: + break; + case OTArc: + break; + case OTSpline: + break; + } +} + diff --git a/src/drawingview.h b/src/drawingview.h index 08f347f..768c5c0 100644 --- a/src/drawingview.h +++ b/src/drawingview.h @@ -25,7 +25,8 @@ class DrawingView: public QWidget void RenderObjects(Painter *, std::vector &, int, bool ignoreLayer = false); void AddHoveredToSelection(void); void GetSelection(std::vector &); - void GetHovered(std::vector &); +// void GetHovered(std::vector &); + std::vector GetHovered(void); void ToolHandler(int, Point); void ToolDraw(Painter *); void LineHandler(int, Point); @@ -34,12 +35,16 @@ class DrawingView: public QWidget void RotateHandler(int, Point); void MirrorHandler(int, Point); void DimensionHandler(int, Point); + void TriangulateHandler(int, Point); + void TrimHandler(int, Point); + void ParallelHandler(int, Point); Rect GetObjectExtents(Object *); void CheckObjectBounds(void); bool HitTestObjects(Point); bool HitTest(Object *, Point); bool HandleObjectClicked(void); void HandleObjectMovement(Point); + void AddDimensionTo(void * obj); public slots: void DeleteCurrentLayer(int); @@ -54,6 +59,7 @@ class DrawingView: public QWidget void ObjectSelected(Object *); protected: + void focusOutEvent(QFocusEvent * event); void paintEvent(QPaintEvent * event); void resizeEvent(QResizeEvent * event); void mousePressEvent(QMouseEvent * event); @@ -69,7 +75,6 @@ class DrawingView: public QWidget public: bool useAntialiasing; -// uint32_t numSelected; uint32_t numHovered; bool shiftDown; bool ctrlDown; @@ -91,10 +96,12 @@ class DrawingView: public QWidget public: std::vector select; - std::vector hover; +// std::vector hover; std::vector toolObjects; std::vector toolScratch; + std::vector toolScratch2; Point toolPoint[32]; + Object * toolObj[32]; Point intersectionPoint; Point hoverPoint; bool hoverPointValid; @@ -102,6 +109,7 @@ class DrawingView: public QWidget Object * dragged; bool draggingObject; bool angleSnap; + bool dirty; }; #endif // __DRAWINGVIEW_H__ diff --git a/src/fileio.cpp b/src/fileio.cpp index 02ab2f5..ace5bfa 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -19,6 +19,9 @@ #include #include #include +#include "applicationwindow.h" +#include "drawingview.h" +#include "global.h" #include "structs.h" /* @@ -125,7 +128,13 @@ enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile }; 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.1\n"); + fprintf(file, "ARCHITEKTONAS DRAWING V1.2\n"); + fprintf(file, "LAYERS %i\n", Global::numLayers); + + for(int i=0; i containerStack; Container * currentTopContainer = drawing; + ResetLayerVectors(); while (!feof(file)) { @@ -197,6 +225,76 @@ enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile }; /*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; + ResetLayerVectors(); + + 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) + { + // Add the extents of the current container + Rect r = ApplicationWindow::drawing->GetObjectExtents((Object *)currentTopContainer); + currentTopContainer->p[0] = r.TopLeft(); + currentTopContainer->p[1] = r.BottomRight(); +//printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b); + + // 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_2(FILE * file, Container * drawing) +{ + int hidden, locked; + char textBuffer[65536]; + + // Load layer information first + fscanf(file, "LAYERS %i\n", &Global::numLayers); + + for(int i=0; iGetObjectExtents((Object *)currentTopContainer); + currentTopContainer->p[0] = r.TopLeft(); + currentTopContainer->p[1] = r.BottomRight(); +//printf("Container extents: <%lf, %lf>, <%lf, %lf>\n", r.l, r.t, r.r, r.b); + // Container is done, so pop the stack to get back the previous TLC currentTopContainer = containerStack.back(); containerStack.pop_back(); @@ -280,6 +384,7 @@ if (errno) } // Need to add pen attributes as well... do that for v1.1 :-P + // And fill attributes... do that for v1.3 (1.2 added layers) if (strcmp(buffer, "LINE") == 0) { Point p1, p2; diff --git a/src/fileio.h b/src/fileio.h index 8aa7511..29a9afe 100644 --- a/src/fileio.h +++ b/src/fileio.h @@ -12,10 +12,12 @@ class FileIO public: static bool SaveAtnsFile(FILE *, Container *); static bool LoadAtnsFile(FILE *, Container *); + static void ResetLayerVectors(void); private: static bool LoadVersion1_0(FILE *, Container *); static bool LoadVersion1_1(FILE *, Container *); + static bool LoadVersion1_2(FILE *, Container *); // static bool GetObjectFromFile(FILE *, Object *, Object **, int *); static Object * GetObjectFromFile(FILE *, bool extended = false); static bool WriteObjectToFile(FILE *, Object *); diff --git a/src/global.cpp b/src/global.cpp index e408a5d..f9e016f 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -52,4 +52,4 @@ int Global::activeLayer = 0; int Global::numLayers = 1; std::vector Global::layerHidden; std::vector Global::layerLocked; - +std::vector Global::layerName; diff --git a/src/global.h b/src/global.h index 4f4d46a..06c04e8 100644 --- a/src/global.h +++ b/src/global.h @@ -5,6 +5,7 @@ // to do any instantiation shite. #include +#include #include #include #include "vector.h" @@ -58,6 +59,7 @@ class Global static int numLayers; static std::vector layerHidden; static std::vector layerLocked; + static std::vector layerName; }; #endif // __GLOBALS_H__ diff --git a/src/layeritemwidget.cpp b/src/layeritemwidget.cpp index 40c8b01..f86017b 100644 --- a/src/layeritemwidget.cpp +++ b/src/layeritemwidget.cpp @@ -31,7 +31,7 @@ LayerItemWidget::LayerItemWidget(QString s, bool i/*=false*/, bool l/*=false*/, // This is required, otherwise the layout engine puts too much space around // this widget. :-/ mainLayout->setContentsMargins(0, 0, 0, 0); - + invisible->setFlat(true); invisible->setIcon(visibleIcon); invisible->setCheckable(true); @@ -62,13 +62,13 @@ LayerItemWidget::~LayerItemWidget() void LayerItemWidget::HandleHideToggle(bool state) { // printf("Eye is: %s\n", !state ? "OPEN" : "closed"); - emit(HideToggled(parent, state)); + emit HideToggled(parent, state); } void LayerItemWidget::HandleLockToggle(bool state) { // printf("Lock is: %s\n", !state ? "OPEN" : "closed"); - emit(LockToggled(parent, state)); + emit LockToggled(parent, state); } diff --git a/src/layerwidget.cpp b/src/layerwidget.cpp index 9ac85a8..9177b20 100644 --- a/src/layerwidget.cpp +++ b/src/layerwidget.cpp @@ -69,8 +69,10 @@ LayerWidget::LayerWidget(void): QWidget(), Global::numLayers = 1; Global::layerHidden.clear(); Global::layerLocked.clear(); + Global::layerName.clear(); Global::layerHidden.push_back(false); Global::layerLocked.push_back(false); + Global::layerName.push_back("Background"); } @@ -79,10 +81,33 @@ LayerWidget::~LayerWidget() } +void LayerWidget::Reload(void) +{ + list->clear(); + + for(int i=0; iinsertItem(0, qlwi); + list->setItemWidget(qlwi, liw); + + // Set up SIGNAL/SLOTs for this LayerItemWidget + connect(liw, SIGNAL(HideToggled(QListWidgetItem *, bool)), this, SLOT(HandleHideToggle(QListWidgetItem *, bool))); + connect(liw, SIGNAL(LockToggled(QListWidgetItem *, bool)), this, SLOT(HandleLockToggle(QListWidgetItem *, bool))); + } + + int layer = (Global::numLayers - Global::activeLayer) - 1; + list->setCurrentRow(layer, QItemSelectionModel::SelectCurrent); + SetButtonStates(); +} + + void LayerWidget::HandleLayerSelected(int currentRow) { -//printf("LayerWidget::HandleLayerSelected(): currentRow = %i\n", currentRow); -// emit(LayerSelected(currentRow)); + // If LayerWidget is empty, bail out + if (currentRow == -1) + return; // This is numbered opposite of how it's presented. In other words, the // bottom of the list is 0, and items above it count upwards. So like this: @@ -93,7 +118,7 @@ void LayerWidget::HandleLayerSelected(int currentRow) // // which is the opposite of the internal numbering. Global::activeLayer = (Global::numLayers - currentRow) - 1; -//printf("LayerWidget::HandleLayerSelected(): currentRow = %i, numLayers = %i, active = %i\n", currentRow, Global::numLayers, Global::activeLayer); + // Set button states to sane values SetButtonStates(); } @@ -114,7 +139,7 @@ void LayerWidget::HandleHideToggle(QListWidgetItem * qlwi, bool state) //printf("Item #%i, new hide state is %s\n", currentRow, (state ? "ON" : "off")); //printf("LayerWidget: New hide state of layer %i is %s.\n", layer, (state ? "ON" : "off")); // We do this last, because otherwise the Document would get the wrong state - emit(LayerToggled()); + emit LayerToggled(); } @@ -136,7 +161,8 @@ void LayerWidget::HandleDblClick(QListWidgetItem * /*qlwi*/) void LayerWidget::AddLayer(void) { - // We always stick the newest layer at the top of the list... + // We always stick the newest layer at the top of the list (visually, the + // top of the list is the end, the bottom is the beginning)... int count = list->count(); QString text = QString("Layer #%1").arg(count); QListWidgetItem * qlwi = new QListWidgetItem(); @@ -151,8 +177,9 @@ void LayerWidget::AddLayer(void) SetButtonStates(); // Fix up the global state - Global::layerHidden.insert(Global::layerHidden.begin(), false); - Global::layerLocked.insert(Global::layerLocked.begin(), false); + Global::layerHidden.push_back(false); + Global::layerLocked.push_back(false); + Global::layerName.push_back(text.toUtf8().data()); Global::numLayers++; } @@ -168,7 +195,7 @@ void LayerWidget::DeleteLayer(void) // HandleLayerSelected() to be fired off which causes the numbers to // be off. You have been warned! // Tell the DrawingView to delete this layer in its Container: - emit(LayerDeleted(Global::activeLayer)); + emit LayerDeleted(Global::activeLayer); int currentRow = list->currentRow(); QListWidgetItem * qlwi = list->currentItem(); @@ -181,6 +208,7 @@ void LayerWidget::DeleteLayer(void) int layer = (Global::numLayers - currentRow) - 1; Global::layerHidden.erase(Global::layerHidden.begin() + layer); Global::layerLocked.erase(Global::layerLocked.begin() + layer); + Global::layerName.erase(Global::layerName.begin() + layer); Global::numLayers--; // If we're deleting from the top of the list, we have to decrement the @@ -201,7 +229,13 @@ void LayerWidget::EditLayer(void) QString result = QInputDialog::getText(this, tr("Edit Layer Name"), tr("Layer Name:"), QLineEdit::Normal, s, &ok); if (ok && !result.isEmpty()) + { li->name->setText(result); + + int layer = (Global::numLayers - Global::activeLayer) - 1; + std::vector::iterator i = Global::layerName.begin() + layer; + (*i) = result.toUtf8().data(); + } } @@ -236,8 +270,11 @@ void LayerWidget::MoveLayerUp(void) old = Global::layerLocked[layer]; Global::layerLocked[layer] = Global::layerLocked[layer + 1]; Global::layerLocked[layer + 1] = old; + std::string oldStr = Global::layerName[layer]; + Global::layerName[layer] = Global::layerName[layer + 1]; + Global::layerName[layer + 1] = oldStr; // We also have to tell the document to shuffle its layers too - emit(LayersSwapped(layer, layer + 1)); + emit LayersSwapped(layer, layer + 1); } @@ -272,8 +309,11 @@ void LayerWidget::MoveLayerDown(void) old = Global::layerLocked[layer]; Global::layerLocked[layer] = Global::layerLocked[layer - 1]; Global::layerLocked[layer - 1] = old; + std::string oldStr = Global::layerName[layer]; + Global::layerName[layer] = Global::layerName[layer - 1]; + Global::layerName[layer - 1] = oldStr; // We also have to tell the document to shuffle its layers too - emit(LayersSwapped(layer, layer - 1)); + emit LayersSwapped(layer, layer - 1); } diff --git a/src/layerwidget.h b/src/layerwidget.h index 875c3cd..5da5ee9 100644 --- a/src/layerwidget.h +++ b/src/layerwidget.h @@ -11,6 +11,9 @@ class LayerWidget: public QWidget LayerWidget(void); ~LayerWidget(); + public slots: + void Reload(void); + private slots: void HandleLayerSelected(int); void HandleHideToggle(QListWidgetItem *, bool); diff --git a/src/structs.h b/src/structs.h index cdd11ec..c1b33fb 100644 --- a/src/structs.h +++ b/src/structs.h @@ -12,10 +12,19 @@ enum ObjectType { OTNone = 0, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTD enum DimensionType { DTLinear = 0, DTLinearVert, DTLinearHorz, DTRadial, DTDiametric, DTCircumferential, DTAngular, DTLeader, DTCount }; -enum ToolType { TTNone, TTLine, TTCircle, TTEllipse, TTArc, TTDimension, TTText, TTPolygon, TTSpline, TTRotate, TTMirror, TTTrim, TTTriangulate, TTDelete }; +enum ToolType { TTNone, TTLine, TTCircle, TTEllipse, TTArc, TTDimension, TTText, TTPolygon, TTSpline, TTRotate, TTMirror, TTTrim, TTTriangulate, TTDelete, TTParallel }; enum ToolState { TSNone, TSPoint1, TSPoint2, TSPoint3, TSPoint4, TSDone }; +const char objName[OTCount][16] = { + "None", "Line", "Circle", "Ellipse", "Arc", "Polygon", "Dimension", + "Spline", "Text", "Container" +}; +const char dimName[DTCount][32] = { + "Linear", "Vertical", "Horizontal", "Radial", "Diametric", + "Circumferential", "Angular", "Leader" +}; + #define OBJECT_COMMON \ int type; \ uint32_t id; \ @@ -29,7 +38,8 @@ enum ToolState { TSNone, TSPoint1, TSPoint2, TSPoint3, TSPoint4, TSDone }; bool hitObject; \ Point p[5]; \ double angle[2]; \ - double radius[2]; + double radius[2]; \ + double length; struct Object { OBJECT_COMMON; @@ -38,10 +48,10 @@ struct Object { struct Line { OBJECT_COMMON; - Line(): type(OTLine), id(Global::objectID++) {} + Line(): type(OTLine), id(Global::objectID++), selected(false), hovered(false), hitObject(false) { hitPoint[0] = hitPoint[1] = false; } Line(Vector pt1, Vector pt2, float th = 1.0, uint32_t c = 0, int l = LSSolid): type(OTLine), id(Global::objectID++), layer(0), color(c), thickness(th), - style(l), selected(false), hovered(false), hitObject(false) { p[0] = pt1; p[1] = pt2; } + style(l), selected(false), hovered(false), hitObject(false) { p[0] = pt1; p[1] = pt2; hitPoint[0] = hitPoint[1] = false; } }; struct Circle { @@ -51,7 +61,7 @@ struct Circle { Circle(Vector pt1, double r, float th = 1.0, uint32_t c = 0, int l = LSSolid): type(OTCircle), id(Global::objectID++), layer(0), color(c), thickness(th), style(l), selected(false), hovered(false), hitObject(false) - { p[0] = pt1; radius[0] = r; } + { p[0] = pt1; radius[0] = r; hitPoint[0] = hitPoint[1] = false; } }; struct Ellipse { @@ -61,7 +71,7 @@ struct Ellipse { Ellipse(Vector pt1, Vector pt2, double r1, double r2, float th = 1.0, uint32_t c = 0, int l = LSSolid): type(OTEllipse), id(Global::objectID++), layer(0), color(c), thickness(th), style(l), selected(false), hovered(false), hitObject(false) - { p[0] = pt1; p[1] = pt2; radius[0] = r1; radius[1] = r2; } + { p[0] = pt1; p[1] = pt2; radius[0] = r1; radius[1] = r2; hitPoint[0] = hitPoint[1] = false; } }; struct Arc { @@ -71,7 +81,7 @@ struct Arc { Arc(Vector pt1, double r, double a1, double a2, float th = 1.0, uint32_t c = 0, int l = LSSolid): type(OTArc), id(Global::objectID++), layer(0), color(c), thickness(th), style(l), selected(false), hovered(false), hitObject(false) - { p[0] = pt1; radius[0] = r; angle[0] = a1, angle[1] = a2; } + { p[0] = pt1; radius[0] = r; angle[0] = a1, angle[1] = a2; hitPoint[0] = hitPoint[1] = hitPoint[2] = false; } }; struct Dimension { @@ -81,11 +91,14 @@ struct Dimension { Point lp[2]; // Line point, the actual dimension line Object * obj[2]; // Pointer to attached objects (circle, lines for angle) - Dimension(): type(OTDimension), id(Global::objectID++) {} + Dimension(): type(OTDimension), id(Global::objectID++), selected(false), + hovered(false), hitObject(false) + { hitPoint[0] = hitPoint[1] = hitPoint[2] = hitPoint[3] = hitPoint[4] = false; } Dimension(Vector pt1, Vector pt2, DimensionType dt = DTLinear, float th = 1.0, uint32_t c = 0x0000FF, int l = LSSolid): type(OTDimension), id(Global::objectID++), layer(0), color(c), thickness(th), style(l), selected(false), hovered(false), - hitObject(false), subtype(dt), offset(0) { p[0] = pt1; p[1] = pt2; } + hitObject(false), subtype(dt), offset(0) + { p[0] = pt1; p[1] = pt2; hitPoint[0] = hitPoint[1] = hitPoint[2] = hitPoint[3] = hitPoint[4] = false; } }; struct Text { @@ -118,8 +131,10 @@ struct Container { std::vector objects; double scale; bool topLevel; + Object * clicked; - Container(bool tl = false): type(OTContainer), id(Global::objectID++), selected(false), hovered(false), hitObject(false), topLevel(tl) {} + Container(bool tl = false): type(OTContainer), id(Global::objectID++), selected(false), hovered(false), hitObject(false), topLevel(tl), clicked(NULL) {} + void Add(void * obj) { objects.push_back(obj); } // void DeleteContents(void) {} /* void DeleteContents(Container * c) { diff --git a/src/utils.cpp b/src/utils.cpp index 2263d7d..98902d7 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,7 +1,7 @@ // utils.cpp: Stuff that's useful to have kicking around, in one spot // // Part of the Architektonas Project -// (C) 2015 Underground Software +// (C) 2020 Underground Software // See the README and GPLv3 files for licensing and warranty information // // JLH = James Hammons @@ -13,6 +13,7 @@ #include "utils.h" #include // For memcpy() +#include "geometry.h" // @@ -21,9 +22,7 @@ // void CopyObjects(std::vector & from, std::vector & to) { - std::vector::iterator i; - - for(i=from.begin(); i!=from.end(); i++) + for(std::vector::iterator i=from.begin(); i!=from.end(); i++) { Object * obj = (Object *)(*i); Object * newObject = CopyObject(obj); @@ -37,45 +36,47 @@ void CopyObjects(std::vector & from, std::vector & to) // Object * CopyObject(Object * obj) { - Object * newObject = NULL; + void * newObject = NULL; switch (obj->type) { case OTLine: - newObject = (Object *)new Line(); + newObject = new Line(); memcpy(newObject, obj, sizeof(Line)); break; case OTCircle: - newObject = (Object *)new Circle(); + newObject = new Circle(); memcpy(newObject, obj, sizeof(Circle)); break; case OTEllipse: - newObject = (Object *)new Ellipse(); + newObject = new Ellipse(); memcpy(newObject, obj, sizeof(Ellipse)); break; case OTArc: - newObject = (Object *)new Arc(); + newObject = new Arc(); memcpy(newObject, obj, sizeof(Arc)); break; case OTDimension: - newObject = (Object *)new Dimension(); + newObject = new Dimension(); memcpy(newObject, obj, sizeof(Dimension)); break; #if 0 case OTSpline: - newObject = (Object *)new Spline(); + newObject = new Spline(); memcpy(newObject, obj, sizeof(Spline)); break; #endif case OTText: - newObject = (Object *)new Text(); + newObject = new Text(); memcpy(newObject, obj, sizeof(Text)); ((Text *)newObject)->s = ((Text *)obj)->s; break; case OTContainer: - newObject = (Object *)new Container(); + newObject = new Container(); //this won't work... // memcpy(newObject, obj, sizeof(Line)); + ((Container *)newObject)->p[0] = obj->p[0]; + ((Container *)newObject)->p[1] = obj->p[1]; CopyObjects(((Container *)obj)->objects, ((Container *)newObject)->objects); break; default: @@ -83,10 +84,10 @@ Object * CopyObject(Object * obj) } // Fix objectID - if (newObject && (newObject->type != OTContainer)) - newObject->id = Global::objectID; + if (newObject && (((Object *)newObject)->type != OTContainer)) + ((Object *)newObject)->id = Global::objectID++; - return newObject; + return (Object *)newObject; } @@ -109,6 +110,23 @@ void MoveSelectedObjectsTo(std::vector & dest, std::vector & fro } +//hmm, this won't work, as these are just pointers... +//[should work now] +void CopySelectedObjectsTo(std::vector & dest, std::vector & from) +{ + for(std::vector::iterator i=from.begin(); i!=from.end(); i++) + { + Object * obj = (Object *)(*i); + + if (obj->selected) +// { +// Object * newObject = CopyObject(obj); + dest.push_back(CopyObject(obj)); +// } + } +} + + void AddObjectsTo(std::vector & dest, std::vector & from) { for(std::vector::iterator i=from.begin(); i!=from.end(); i++) @@ -136,11 +154,11 @@ void SelectAll(std::vector & v) // // 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 +// 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). ;-) +// have to keep track of that stuff ourselves. :-P Believe it or not, this is +// a Good Thing(TM). ;-) // void DeleteContents(std::vector & v) { @@ -155,8 +173,11 @@ void DeleteContents(std::vector & v) delete obj; } + + v.clear(); } + void DeleteSelectedObjects(std::vector & v) { std::vector::iterator i = v.begin(); @@ -203,7 +224,7 @@ void SavePointsFrom(std::vector & v, std::vector & save) for(std::vector::iterator i=v.begin(); i!=v.end(); i++) { - memcpy(&o, (*i), sizeof(Object)); + memcpy(&o, (Object *)(*i), sizeof(Object)); save.push_back(o); } } @@ -227,6 +248,32 @@ void RestorePointsTo(std::vector & v, std::vector & s) } +void RestorePointsTo(std::vector & v, std::vector & s) +{ + std::vector::iterator i = s.begin(); + std::vector::iterator j = v.begin(); + + for(; i!=s.end(); i++, j++) + { + Object * objS = (Object *)(*i); + Object * objV = (Object *)(*j); + + if (objV->type == OTContainer) + { + RestorePointsTo(((Container *)objV)->objects, ((Container *)objS)->objects); + return; + } + + objV->p[0] = objS->p[0]; + objV->p[1] = objS->p[1]; + objV->angle[0] = objS->angle[0]; + objV->angle[1] = objS->angle[1]; +//we don't do this because we want to keep selected & friends from changing +// memcpy(obj2, *j, sizeof(Object)); + } +} + + void TranslateObject(Object * obj, Point delta) { if (obj->type == OTContainer) @@ -237,11 +284,61 @@ void TranslateObject(Object * obj, Point delta) for(i=c->objects.begin(); i!=c->objects.end(); i++) TranslateObject((Object *)*i, delta); } - else - { +// else +// { obj->p[0] += delta; obj->p[1] += delta; +// } +} + + +/* +So we need to make it so that we pick the container's point clicked on, and translate all the other parts *not* clicked on. +*/ +void TranslateContainer(Container * c, Point point, Point delta) +{ + if (c->clicked == NULL) + { +// TranslateObject((Object *)c, delta); + return; } + +static int i=0; +printf("TranslateContainer: boop (%i)\n", i++); + Point clickedPoint; + + switch (c->clicked->type) + { + case OTLine: + if (c->clicked->hitPoint[0]) + clickedPoint = c->clicked->p[0]; + else if (c->clicked->hitPoint[1]) + clickedPoint = c->clicked->p[1]; + else if (c->clicked->hitObject) + clickedPoint = Geometry::Midpoint((Line *)(c->clicked)); + + break; + + case OTCircle: + if (c->clicked->hitPoint[0]) + clickedPoint = c->clicked->p[0]; + else if (c->clicked->hitObject) + clickedPoint = point; + + break; + + case OTArc: + break; + + case OTDimension: + break; + + case OTText: + break; + } + + Point clickedDelta = point - clickedPoint; + TranslateObject((Object *)c, clickedDelta); } @@ -274,3 +371,24 @@ void TranslateObjects(std::vector & v, Point delta) } +std::vector Flatten(Container * src) +{ + std::vector flat; + std::vector::iterator i; + + for(i=src->objects.begin(); i!=src->objects.end(); i++) + { + flat.push_back(*i); + Object * obj = (Object *)(*i); + + // Recursively add objects to the flat vector, if necessary + if (obj->type == OTContainer) + { + std::vector sub = Flatten((Container *)obj); + flat.insert(flat.end(), sub.begin(), sub.end()); + } + } + + return flat; +} + diff --git a/src/utils.h b/src/utils.h index 90e6d9f..a72ca39 100644 --- a/src/utils.h +++ b/src/utils.h @@ -7,6 +7,7 @@ void CopyObjects(std::vector & from, std::vector & to); Object * CopyObject(Object * obj); void MoveSelectedObjectsTo(std::vector & dest, std::vector & from); +void CopySelectedObjectsTo(std::vector & dest, std::vector & from); void AddObjectsTo(std::vector & dest, std::vector & from); void ClearSelected(std::vector & v); void SelectAll(std::vector & v); @@ -15,8 +16,10 @@ 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 RestorePointsTo(std::vector & v, std::vector & s); void TranslateObject(Object * obj, Point delta); +void TranslateContainer(Container * c, Point point, Point delta); void TranslateObjects(std::vector & v, Point delta); +std::vector Flatten(Container * src); #endif // __UTILS_H__ -