]> Shamusworld >> Repos - architektonas/commitdiff
Miscellaneous fixes/updates:
authorShamus Hammons <jlhamm@acm.org>
Mon, 29 Nov 2021 21:53:15 +0000 (15:53 -0600)
committerShamus Hammons <jlhamm@acm.org>
Mon, 29 Nov 2021 21:53:15 +0000 (15:53 -0600)
 - Added default copy constructor to Vector class, to stop spurious compiler
   warnings
 - Added Polyline object to structs definition
 - Added tangent detection (point-circle, circle-circle)
 - Added initial line to circle tangent attachment
 - Added beginning of parallel tool (UI hookup done, tool code needs work)
 - Fixed intersection detection
 - Added new command line entry UI

17 files changed:
TODO
architektonas.pro
res/architektonas.qrc
res/parallel-tool.png [new file with mode: 0644]
src/applicationwindow.cpp
src/applicationwindow.h
src/consolewidget.cpp [new file with mode: 0644]
src/consolewidget.h [new file with mode: 0644]
src/drawingview.cpp
src/drawingview.h
src/geometry.cpp
src/geometry.h
src/promptlineedit.cpp [new file with mode: 0644]
src/promptlineedit.h [new file with mode: 0644]
src/structs.h
src/vector.cpp
src/vector.h

diff --git a/TODO b/TODO
index eb6bfb82cc11c8ea1483817afd34301550ae21a2..23a36b3f39db89d3d6f01f23b5109edc26d09691 100644 (file)
--- a/TODO
+++ b/TODO
@@ -22,7 +22,8 @@ Stuff To Be Implemented/Fixed
    remove it and it only, or to cut the entity at other entities crossing)
  - Make Architektonas an MDI application
  - Parallel tool (be able to make copy of object that's parallel to original)
- - Pen marker doesn't set attributes on the highlighted part of the group.
+ - Add command line tool
+ - Be able to show dimensions in pure inches as well as feet & inches
 
 
 Stuff That's Done
@@ -52,3 +53,14 @@ Things to Ponder
 ----------------
 
 Grouping items on different layers currently makes the items *not* on the current layer disappear.  Not sure how this should be handled...  Maybe we should honor the top level Container's attributes, and not the attributes of the contained objects?
+
+
+Bugs
+----
+
+ - Add new dimension doesn't honor the scale parameter set by user
+ - Dimension object prevents system from finding intersections when near or on
+   object intersection
+ - Pen marker doesn't set attributes on the highlighted part of the group.
+ - Can't move objects to other layers
+ - Dimension lines extents aren't properly taken into account when printing
index 2af6bc767fcc62ad03e1cc04dc5006ab6f7f3e31..4a13c0a0d7941b13d2f572aa5e24c9b8aa8fee43 100644 (file)
@@ -52,6 +52,7 @@ HEADERS = \
        src/baseunittab.h \
        src/blockitemwidget.h \
        src/blockwidget.h \
+       src/consolewidget.h \
        src/drawingview.h \
        src/fileio.h \
        src/generaltab.h \
@@ -62,8 +63,9 @@ HEADERS = \
        src/main.h \
        src/mathconstants.h \
        src/objectwidget.h \
-       src/penwidget.h \
        src/painter.h \
+       src/penwidget.h \
+       src/promptlineedit.h \
        src/rect.h \
        src/settingsdialog.h \
        src/structs.h \
@@ -76,6 +78,7 @@ SOURCES = \
        src/baseunittab.cpp \
        src/blockitemwidget.cpp \
        src/blockwidget.cpp \
+       src/consolewidget.cpp \
        src/drawingview.cpp \
        src/fileio.cpp \
        src/generaltab.cpp \
@@ -85,8 +88,9 @@ SOURCES = \
        src/layeritemwidget.cpp \
        src/main.cpp \
        src/objectwidget.cpp \
-       src/penwidget.cpp \
        src/painter.cpp \
+       src/penwidget.cpp \
+       src/promptlineedit.cpp \
        src/rect.cpp \
        src/settingsdialog.cpp \
        src/utils.cpp \
index 0929bebab1e634943143739dd4461d82be2021c9..83fbfc47ae7edacaf955c826a32a5d09f7671906 100644 (file)
@@ -48,5 +48,6 @@
                <file>cursor-marker.png</file>
                <file>cursor-dropper.png</file>
                <file>print-preview.png</file>
+               <file>parallel-tool.png</file>
        </qresource>
 </RCC>
diff --git a/res/parallel-tool.png b/res/parallel-tool.png
new file mode 100644 (file)
index 0000000..ed0fe2f
Binary files /dev/null and b/res/parallel-tool.png differ
index b80130e8989cf77740f5e66ad97dcc9ba801c092..2f78dfc61a6f92ac53229faba2091adea2ac1847 100644 (file)
@@ -31,6 +31,7 @@
 #include <QPrintPreviewDialog>
 #include "about.h"
 #include "blockwidget.h"
+#include "consolewidget.h"
 #include "drawingview.h"
 #include "fileio.h"
 #include "generaltab.h"
@@ -80,10 +81,16 @@ ApplicationWindow::ApplicationWindow():
        ObjectWidget * ow = new ObjectWidget;
        dock3->setWidget(ow);
        addDockWidget(Qt::RightDockWidgetArea, dock3);
+       QDockWidget * dock4 = new QDockWidget(tr("Command"), this);
+       ConsoleWidget * cw = new ConsoleWidget;
+       dock4->setWidget(cw);
+       addDockWidget(Qt::BottomDockWidgetArea, dock4);
+
        // Needed for saveState()
        dock1->setObjectName("Layers");
        dock2->setObjectName("Blocks");
        dock3->setObjectName("Object");
+       dock4->setObjectName("Commands");
 
        // Create status bar
        zoomIndicator = new QLabel("Zoom: 100% Grid: 12.0\" BU: Inch");
@@ -300,6 +307,7 @@ void ApplicationWindow::HandlePrintRequest(QPrinter * printer)
 
 void ApplicationWindow::contextMenuEvent(QContextMenuEvent * event)
 {
+       // Proof of concept, still need to code up the real thing
        QMenu menu(this);
        menu.addAction(mirrorAct);
        menu.addAction(rotateAct);
@@ -374,6 +382,12 @@ void ApplicationWindow::TrimTool(void)
        SetInternalToolStates();
 }
 
+void ApplicationWindow::ParallelTool(void)
+{
+       ClearUIToolStatesExcept(parallelAct);
+       SetInternalToolStates();
+}
+
 void ApplicationWindow::TriangulateTool(void)
 {
        ClearUIToolStatesExcept(triangulateAct);
@@ -516,6 +530,8 @@ void ApplicationWindow::SetInternalToolStates(void)
                Global::tool = TTRotate;
        else if (trimAct->isChecked())
                Global::tool = TTTrim;
+       else if (parallelAct->isChecked())
+               Global::tool = TTParallel;
        else if (triangulateAct->isChecked())
                Global::tool = TTTriangulate;
        else
@@ -846,6 +862,9 @@ void ApplicationWindow::CreateActions(void)
        trimAct = CreateAction(tr("&Trim"), tr("Trim"), tr("Trim extraneous lines from selected objects."), QIcon(":/res/trim-tool.png"), QKeySequence("t,r"), true);
        connect(trimAct, SIGNAL(triggered()), this, SLOT(TrimTool()));
 
+       parallelAct = CreateAction(tr("&Parallel"), tr("Parallel"), tr("Create copies of objects parallel to the original."), QIcon(":/res/parallel-tool.png"), QKeySequence("p,a"), true);
+       connect(parallelAct, SIGNAL(triggered()), this, SLOT(ParallelTool()));
+
        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()));
 
@@ -934,6 +953,7 @@ void ApplicationWindow::CreateMenus(void)
        menu->addAction(rotateAct);
        menu->addAction(mirrorAct);
        menu->addAction(trimAct);
+       menu->addAction(parallelAct);
        menu->addAction(triangulateAct);
        menu->addAction(connectAct);
        menu->addAction(disconnectAct);
@@ -988,6 +1008,7 @@ void ApplicationWindow::CreateToolbars(void)
        toolbar->addAction(rotateAct);
        toolbar->addAction(mirrorAct);
        toolbar->addAction(trimAct);
+       toolbar->addAction(parallelAct);
        toolbar->addAction(triangulateAct);
        toolbar->addAction(editCutAct);
        toolbar->addAction(editCopyAct);
index 00c79178601c974c66075d9e1082fa06af97ee59..a83e93722202aab35a421bccbdde683752d6d428 100644 (file)
@@ -37,6 +37,7 @@ class ApplicationWindow: public QMainWindow
                void RotateTool(void);
                void MirrorTool(void);
                void TrimTool(void);
+               void ParallelTool(void);
                void TriangulateTool(void);
                void AddLineTool(void);
                void AddCircleTool(void);
@@ -109,6 +110,7 @@ class ApplicationWindow: public QMainWindow
                QAction * disconnectAct;
                QAction * mirrorAct;
                QAction * trimAct;
+               QAction * parallelAct;
                QAction * triangulateAct;
                QAction * editCutAct;
                QAction * editCopyAct;
@@ -124,4 +126,3 @@ class ApplicationWindow: public QMainWindow
 };
 
 #endif // __APPLICATIONWINDOW_H__
-
diff --git a/src/consolewidget.cpp b/src/consolewidget.cpp
new file mode 100644 (file)
index 0000000..ce55cc4
--- /dev/null
@@ -0,0 +1,58 @@
+//
+// consolewidget.cpp: Command line widget
+//
+// Part of the Architektonas Project
+// (C) 2021 Underground Software
+// See the README and GPLv3 files for licensing and warranty information
+//
+// JLH = James Hammons <jlhamm@acm.org>
+//
+// WHO  WHEN        WHAT
+// ---  ----------  ------------------------------------------------------------
+// JLH  11/20/2021  Created this file
+//
+
+#include "consolewidget.h"
+
+ConsoleWidget::ConsoleWidget(QWidget * parent/*= NULL*/): QWidget(parent)
+{
+       cmdline = new PromptLineEdit(this);
+       cmdline->setFrame(false);
+
+       screen = new QTextEdit;
+       screen->setAlignment(Qt::AlignBottom | Qt::AlignLeft);
+       screen->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+       screen->setReadOnly(true);
+
+       QVBoxLayout * mainLayout = new QVBoxLayout;
+       mainLayout->addWidget(screen);
+       mainLayout->addWidget(cmdline);
+
+       setLayout(mainLayout);
+
+       connect(cmdline, SIGNAL(returnPressed()), this, SLOT(Execute()));
+
+       QScrollBar * scrollbar = screen->verticalScrollBar();
+       connect(scrollbar, SIGNAL(rangeChanged(int, int)), this, SLOT(MoveScrollBarToBottom(int, int)));
+}
+
+ConsoleWidget::~ConsoleWidget()
+{
+}
+
+void ConsoleWidget::Execute(void)
+{
+       screen->append(cmdline->text());
+       screen->append("<font color=red>Error: don't know how to '" + cmdline->text() + "'</font>");
+       cmdline->clear();
+}
+
+void ConsoleWidget::MoveScrollBarToBottom(int min, int max)
+{
+       Q_UNUSED(min);
+       screen->verticalScrollBar()->setValue(max);
+}
+
+void ConsoleWidget::paintEvent(QPaintEvent * /*event*/)
+{
+}
diff --git a/src/consolewidget.h b/src/consolewidget.h
new file mode 100644 (file)
index 0000000..147b597
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef __CONSOLEWIDGET_H__
+#define __CONSOLEWIDGET_H__
+
+#include <QtWidgets>
+#include "promptlineedit.h"
+
+class ConsoleWidget: public QWidget
+{
+       Q_OBJECT
+
+       public:
+               ConsoleWidget(QWidget * parent = NULL);
+               ~ConsoleWidget();
+
+       public slots:
+               void Execute(void);
+               void MoveScrollBarToBottom(int min, int max);
+
+       protected:
+               void paintEvent(QPaintEvent * event);
+
+       public:
+               PromptLineEdit * cmdline;
+               QTextEdit * screen;
+};
+
+#endif // __CONSOLEWIDGET_H__
index 911ca677dd3b4269bde8f864038faba0ef518120..9acf00cd7e29fb1878b4c9c04a9c7d856c3fe68a 100644 (file)
@@ -686,6 +686,11 @@ Where is the text offset?  It looks like it's drawing in the center, but obvious
                        break;
                }
 
+               case OTPolyline:
+               {
+                       break;
+               }
+
                case OTContainer:
                {
                        // Containers require recursive rendering...
@@ -739,14 +744,30 @@ VPVector DrawingView::GetSelection(void)
        return v;
 }
 
-VPVector DrawingView::GetHovered(void)
+//
+// When testing for hovered intersections, we need to be able to exclude some
+// objects which have funky characteristics or handles; so we allow for that
+// here.
+//
+VPVector DrawingView::GetHovered(bool exclude/*= false*/)
 {
        VPVector v;
 
        for(VPVectorIter i=document.objects.begin(); i!=document.objects.end(); i++)
        {
-               if (((Object *)(*i))->hovered)
+               Object * obj = (Object *)(*i);
+
+               if (obj->hovered)
+               {
+                       if (exclude
+                               && ((obj->type == OTDimension)
+                                       || ((obj->type == OTCircle) && (obj->hitPoint[0] == true))
+                                       || ((obj->type == OTArc) && (obj->hitPoint[0] == true))
+                                       || (draggingObject && (obj == dragged))))
+                               continue;
+
                        v.push_back(*i);
+               }
        }
 
        return v;
@@ -946,6 +967,19 @@ void DrawingView::LineHandler(int mode, Point p)
        switch (mode)
        {
        case ToolMouseDown:
+/*             toolObj[0] = NULL;
+
+               // Check to see if we can do a circle tangent snap
+               if (numHovered == 1)
+               {
+                       VPVector hover = GetHovered();
+                       Object * obj = (Object *)hover[0];
+
+                       // Save for later if the object clicked was a circle (need to check that it wasn't the center clicked on, because that will fuck up connecting centers of circles with lines... and now we do! :-)
+                       if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
+                               toolObj[0] = obj;
+               }*/
+
                if (Global::toolState == TSNone)
                        toolPoint[0] = p;
                else
@@ -957,7 +991,54 @@ void DrawingView::LineHandler(int mode, Point p)
                if (Global::toolState == TSNone)
                        toolPoint[0] = p;
                else
+               {
                        toolPoint[1] = p;
+/*                     bool isCircle = false;
+
+                       if (numHovered == 1)
+                       {
+                               VPVector hover = GetHovered();
+                               Object * obj = (Object *)hover[0];
+
+                               if ((obj->type == OTCircle) && (obj->hitPoint[0] == false))
+                               {
+                                       isCircle = true;
+                                       toolObj[1] = obj;
+                               }
+                       }
+
+                       // Adjust initial point if it's on a circle (tangent point)
+                       if (toolObj[0] != NULL)
+                       {
+                               if (isCircle)
+                               {
+                                       Geometry::FindTangents(toolObj[0], toolObj[1]);
+
+                                       if (Global::numIntersectPoints > 0)
+                                       {
+                                               toolPoint[0] = Global::intersectPoint[0];
+                                               toolPoint[1] = Global::intersectPoint[1];
+                                       }
+                               }
+                               else
+                               {
+                                       Geometry::FindTangents(toolObj[0], p);
+
+                                       if (Global::numIntersectPoints > 0)
+                                               toolPoint[0] = Global::intersectPoint[0];
+                               }
+                       }
+                       else
+                       {
+                               if (isCircle)
+                               {
+                                       Geometry::FindTangents(toolObj[1], toolPoint[0]);
+
+                                       if (Global::numIntersectPoints > 0)
+                                               toolPoint[1] = Global::intersectPoint[0];
+                               }
+                       }*/
+               }
 
                break;
 
@@ -1506,7 +1587,8 @@ void DrawingView::TriangulateHandler(int mode, Point/*p*/)
 void DrawingView::TrimHandler(int mode, Point p)
 {
 /*
-n.b.: this code is lifted straight out of the old oo code.  needs to be updated.
+N.B.: this code is lifted straight out of the old oo code.  needs to be updated.
+      Also: trim tool should ignore snap.
 */
        switch (mode)
        {
@@ -1837,7 +1919,7 @@ void DrawingView::mouseMoveEvent(QMouseEvent * event)
 
        // Do object hit testing...
        bool needUpdate = HitTestObjects(point);
-       VPVector hover2 = GetHovered();
+       VPVector hover2 = GetHovered(true); // Exclude dimension objects and circle centers (probably need to add arc centers too) from hover (also dragged objects...)
 #if 0
 {
 if (needUpdate)
@@ -1851,9 +1933,9 @@ if (needUpdate)
 #endif
 
        // Check for multi-hover...
-       if (numHovered > 1)
+       if (hover2.size() > 1)
        {
-//need to check for case where hover is over 2 circles and a 3rd's center...
+//need to check for case where hover is over 2 circles and a 3rd's center (no longer a problem, I think)...
                Object * obj1 = (Object *)hover2[0], * obj2 = (Object *)hover2[1];
 
                Geometry::Intersects(obj1, obj2);
@@ -1892,7 +1974,7 @@ if (needUpdate)
                        intersectionPoint = v1;
                }
        }
-       else if (numHovered == 1)
+       else if (hover2.size() == 1)
        {
                Object * obj = (Object *)hover2[0];
 
@@ -1900,7 +1982,6 @@ if (needUpdate)
                {
 /*
 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);
@@ -1914,6 +1995,30 @@ Except when lines are overlapping, then it doesn't work... !!! FIX !!!
                                needUpdate = true;
                        }
                }
+               else if (obj->type == OTCircle)
+               {
+                       if ((draggingObject && (dragged->type == OTLine)) && (dragged->hitPoint[0] || dragged->hitPoint[1]))
+                       {
+                               Point p = (dragged->hitPoint[0] ? dragged->p[1] : dragged->p[0]);
+                               Geometry::FindTangents(obj, p);
+
+                               if (Global::numIntersectPoints > 0)
+                               {
+                                       hoveringIntersection = true;
+                                       intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
+                               }
+                       }
+                       else if ((Global::tool == TTLine) && (Global::toolState == TSPoint2))
+                       {
+                               Geometry::FindTangents(obj, toolPoint[0]);
+
+                               if (Global::numIntersectPoints > 0)
+                               {
+                                       hoveringIntersection = true;
+                                       intersectionPoint = Geometry::NearestTo(point, Global::intersectPoint[0], Global::intersectPoint[1]);
+                               }
+                       }
+               }
        }
 
        // Handle object movement (left button down & over an object)
@@ -2254,7 +2359,7 @@ void DrawingView::CheckObjectBounds(void)
                switch (obj->type)
                {
                case OTLine:
-               case OTDimension:
+               case OTDimension: // N.B.: We don't check this properly...
                {
                        Line * l = (Line *)obj;
 
index 704b79d357f60b1b610a0afbf4d802ea8f271fd1..344428a804f65549c348eeb9ab3c43d612a7446f 100644 (file)
@@ -24,7 +24,7 @@ class DrawingView: public QWidget
                void RenderObjects(Painter *, VPVector &, int, bool ignoreLayer = false);
                void AddHoveredToSelection(void);
                VPVector GetSelection(void);
-               VPVector GetHovered(void);
+               VPVector GetHovered(bool exclude = false);
                void ToolHandler(int, Point);
                void ToolDraw(Painter *);
                void LineHandler(int, Point);
@@ -122,4 +122,3 @@ class DrawingView: public QWidget
 };
 
 #endif // __DRAWINGVIEW_H__
-
index 95d12c65068b16715086191e1d70c7e01483dcbd..86d1906f764851e7b28b5172292ab60aaac1b4f9 100644 (file)
@@ -1,3 +1,4 @@
+//
 // geometry.cpp: Algebraic geometry helper functions
 //
 // Part of the Architektonas Project
@@ -19,7 +20,6 @@
 #include "global.h"
 #include "mathconstants.h"
 
-
 // Returns the parameter of a point in space to this vector. If the parameter
 // is between 0 and 1, the normal of the vector to the point is on the vector.
 // Note: lp1 is the tail, lp2 is the head of the line (vector).
@@ -35,10 +35,10 @@ double Geometry::ParameterOfLineAndPoint(Point tail, Point head, Point point)
        double magnitude = lineSegment.Magnitude();
        Vector pointSegment = point - tail;
        double t = lineSegment.Dot(pointSegment) / (magnitude * magnitude);
+
        return t;
 }
 
-
 double Geometry::DistanceToLineFromPoint(Point tail, Point head, Point point)
 {
        // Interpretation: given a line in the form x = a + tu, where u is the
@@ -61,7 +61,6 @@ double Geometry::DistanceToLineFromPoint(Point tail, Point head, Point point)
        return dist.Magnitude() * (angle < HALF_TAU ? +1.0 : -1.0);
 }
 
-
 Point Geometry::MirrorPointAroundLine(Point point, Point tail, Point head)
 {
        // Get the vector of the intersection of the line and the normal on the
@@ -81,7 +80,6 @@ Point Geometry::MirrorPointAroundLine(Point point, Point tail, Point head)
        return mirroredPoint;
 }
 
-
 //
 // point: The point we're rotating
 // rotationPoint: The point we're rotating around
@@ -95,13 +93,11 @@ Point Geometry::RotatePointAroundPoint(Point point, Point rotationPoint, double
        return Vector(rotationPoint.x + px, rotationPoint.y + py, 0);
 }
 
-
 double Geometry::Determinant(Point p1, Point p2)
 {
        return (p1.x * p2.y) - (p2.x * p1.y);
 }
 
-
 void Geometry::Intersects(Object * obj1, Object * obj2)
 {
        Global::numIntersectPoints = Global::numIntersectParams = 0;
@@ -116,7 +112,6 @@ void Geometry::Intersects(Object * obj1, Object * obj2)
                CheckLineToCircleIntersection(obj2, obj1);
 }
 
-
 /*
 Intersecting line segments:
 An easier way:
@@ -182,7 +177,6 @@ void Geometry::CheckLineToLineIntersection(Object * l1, Object * l2)
                Global::numIntersectParams = 1;
 }
 
-
 void Geometry::CheckCircleToCircleIntersection(Object * c1, Object * c2)
 {
        // Set up global vars
@@ -236,7 +230,6 @@ void Geometry::CheckCircleToCircleIntersection(Object * c1, Object * c2)
        Global::numIntersectPoints = 2;
 }
 
-
 //
 // N.B.: l is the line, c is the circle
 //
@@ -300,7 +293,6 @@ void Geometry::CheckLineToCircleIntersection(Object * l, Object * c)
        }
 }
 
-
 // should we just do common trig solves, like AAS, ASA, SAS, SSA?
 // Law of Cosines:
 // c² = a² + b² - 2ab * cos(C)
@@ -349,7 +341,6 @@ hmm... dunno...
                *a3 = angle3;
 }
 
-
 Point Geometry::GetPointForParameter(Object * obj, double t)
 {
        if (obj->type == OTLine)
@@ -363,22 +354,126 @@ Point Geometry::GetPointForParameter(Object * obj, double t)
        return Point(0, 0);
 }
 
-
 Point Geometry::Midpoint(Line * line)
 {
        return Point((line->p[0].x + line->p[1].x) / 2.0,
                (line->p[0].y + line->p[1].y) / 2.0);
 }
 
-
 /*
 How to find the tangent of a point off a circle:
 
- â€¢  Calculate the midpoint on the point and the center of the circle
- â€¢  Get the length of the line segment from the and the center divided by two
+ â€¢  Calculate the midpoint of the point and the center of the circle
+ â€¢  Get the length of the line segment from point and the center divided by two
  â€¢  Use that length to construct a circle with the point at the center and the
     radius equal to that length
  â€¢  The intersection of the two circles are the tangent points
 
+Another way:
+
+ â€¢  Find angle between radius and line between point and center
+ â€¢  Angle +/- from line (point to center) are the tangent points
+
+*/
+void Geometry::FindTangents(Object * c, Point p)
+{
+       // Set up global vars
+       Global::numIntersectPoints = Global::numIntersectParams = 0;
+
+       // Get the distance between the point and the center of the circle, the
+       // length, and the angle.
+       Vector centerLine(c->p[0], p);
+       double d = centerLine.Magnitude();
+       double clAngle = centerLine.Angle();
+
+       // If the point is on or inside the circle, there are no tangents.
+       if (d <= c->radius[0])
+               return;
+
+       // Use 'cos(a) = adjacent / hypotenuse' to get the tangent angle
+       double a = acos(c->radius[0] / d);
+
+       // Finally, find the points of intersection by using +/- the angle found
+       // from the centerline's angle
+       Global::intersectPoint[0].x = c->p[0].x + (cos(clAngle + a) * c->radius[0]);
+       Global::intersectPoint[0].y = c->p[0].y + (sin(clAngle + a) * c->radius[0]);
+       Global::intersectPoint[1].x = c->p[0].x + (cos(clAngle - a) * c->radius[0]);
+       Global::intersectPoint[1].y = c->p[0].y + (sin(clAngle - a) * c->radius[0]);
+       Global::numIntersectPoints = 2;
+}
+
+/*
+The extangents can be found by collapsing the circle of smaller radius to a point, and diminishing the larger circle by the radius of the smaller, then treating it like the point-circle case (you have to translate the tangent back out by the length of the radius of the smaller circle once found).
+
+Not sure what the analogous method for finding the intangents are...  :-/
+Looks like the intangent can be found by augmenting the larger circle by the smaller radius, and doing the center of the smaller as the point-circle case again, only translating the line back by the radius of the smaller.
 */
+void Geometry::FindTangents(Object * c1, Object * c2)
+{
+       // Set up global vars
+       Global::numIntersectPoints = Global::numIntersectParams = 0;
+
+       // Find the larger and smaller of the two:
+       Object * cLg = c1, * cSm = c2;
+
+       if (c2->radius[0] > c1->radius[0])
+               cLg = c2, cSm = c1;
+
+       // Get the distance between the point and the center of the circle, the
+       // length, and the angle.
+       Vector centerLine(cLg->p[0], cSm->p[0]);
+       double d = centerLine.Magnitude();
+       double clAngle = centerLine.Angle();
+
+       // If one circle is completely inside the other, there are no tangents.
+       if ((d + cSm->radius[0]) <= cLg->radius[0])
+               return;
 
+       // Subtract the radius of the smaller from the larger, and use the point-circle method to find the extangent:
+       double a = acos((cLg->radius[0] - cSm->radius[0]) / d);
+
+       // Finally, find the points of intersection by using +/- the angle found
+       // from the centerline's angle
+       Global::intersectPoint[0].x = cLg->p[0].x + (cos(clAngle + a) * cLg->radius[0]);
+       Global::intersectPoint[0].y = cLg->p[0].y + (sin(clAngle + a) * cLg->radius[0]);
+       Global::intersectPoint[1].x = cSm->p[0].x + (cos(clAngle + a) * cSm->radius[0]);
+       Global::intersectPoint[1].y = cSm->p[0].y + (sin(clAngle + a) * cSm->radius[0]);
+
+       Global::intersectPoint[2].x = cLg->p[0].x + (cos(clAngle - a) * cLg->radius[0]);
+       Global::intersectPoint[2].y = cLg->p[0].y + (sin(clAngle - a) * cLg->radius[0]);
+       Global::intersectPoint[3].x = cSm->p[0].x + (cos(clAngle - a) * cSm->radius[0]);
+       Global::intersectPoint[3].y = cSm->p[0].y + (sin(clAngle - a) * cSm->radius[0]);
+       Global::numIntersectPoints = 4;
+
+       // If the circles overlap, there are no intangents.
+       if (d <= (cLg->radius[0] + cSm->radius[0]))
+               return;
+
+       // Add the radius of the smaller from the larger, and use the point-circle method to find the intangent:
+       a = acos((cLg->radius[0] + cSm->radius[0]) / d);
+
+       // Finally, find the points of intersection by using +/- the angle found
+       // from the centerline's angle
+       Global::intersectPoint[4].x = cLg->p[0].x + (cos(clAngle + a) * cLg->radius[0]);
+       Global::intersectPoint[4].y = cLg->p[0].y + (sin(clAngle + a) * cLg->radius[0]);
+       Global::intersectPoint[5].x = cSm->p[0].x + (cos(clAngle - a) * cSm->radius[0]);
+       Global::intersectPoint[5].y = cSm->p[0].y + (sin(clAngle - a) * cSm->radius[0]);
+
+       Global::intersectPoint[6].x = cLg->p[0].x + (cos(clAngle - a) * cLg->radius[0]);
+       Global::intersectPoint[6].y = cLg->p[0].y + (sin(clAngle - a) * cLg->radius[0]);
+       Global::intersectPoint[7].x = cSm->p[0].x + (cos(clAngle + a) * cSm->radius[0]);
+       Global::intersectPoint[7].y = cSm->p[0].y + (sin(clAngle + a) * cSm->radius[0]);
+       Global::numIntersectPoints = 8;
+}
+
+//
+// Parameter 1: point in question
+// Parameter 2, 3: points we are comparing to
+//
+Point Geometry::NearestTo(Point point, Point p1, Point p2)
+{
+       double l1 = Vector::Magnitude(point, p1);
+       double l2 = Vector::Magnitude(point, p2);
+
+       return (l1 < l2 ? p1 : p2);
+}
index bc532da4a9a3e0e206a06144ba9b01dff22ab1bd..58252b3d1ac6d6291fb590d3d198e1768c2bb8d5 100644 (file)
@@ -20,7 +20,9 @@ class Geometry
                static void FindAnglesForSides(double s1, double s2, double s3, double * a1, double * a2, double * a3);
                static Point GetPointForParameter(Object *, double);
                static Point Midpoint(Line *);
+               static void FindTangents(Object *, Point);
+               static void FindTangents(Object *, Object *);
+               static Point NearestTo(Point, Point, Point);
 };
 
 #endif         // __GEOMETRY_H__
-
diff --git a/src/promptlineedit.cpp b/src/promptlineedit.cpp
new file mode 100644 (file)
index 0000000..f79878d
--- /dev/null
@@ -0,0 +1,80 @@
+//
+// promptlineedit.cpp: QLineEdit widget with uneditable (by user) prompt
+//
+// Part of the Architektonas Project
+// (C) 2021 Underground Software
+// See the README and GPLv3 files for licensing and warranty information
+//
+// JLH = James Hammons <jlhamm@acm.org>
+//
+// WHO  WHEN        WHAT
+// ---  ----------  ------------------------------------------------------------
+// JLH  11/27/2021  Created this file
+//
+
+#include "promptlineedit.h"
+
+PromptLineEdit::PromptLineEdit(QWidget * parent/*= NULL*/): QLineEdit(parent)
+{
+       connect(this, SIGNAL(cursorPositionChanged(int, int)), this, SLOT(HandleCursorMoved(int, int)));
+       connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(HandleTextChanged(const QString &)));
+       connect(this, SIGNAL(selectionChanged(void)), this, SLOT(HandleSelectionChanged(void)));
+
+       SetPrompt("ATNS");
+}
+
+PromptLineEdit::~PromptLineEdit()
+{
+}
+
+void PromptLineEdit::HandleCursorMoved(int /*old*/, int neu)
+{
+       if (neu < prompt.length())
+               setCursorPosition(prompt.length());
+}
+
+void PromptLineEdit::HandleTextChanged(const QString & s)
+{
+       if (s.length() == 0)
+               setText(prompt);
+}
+
+void PromptLineEdit::HandleSelectionChanged(void)
+{
+       int selStart = selectionStart();
+       int selEnd = selectionEnd();
+       int promptLen = prompt.length();
+
+       if (selStart == -1)
+               return;
+
+       if (selStart < promptLen)
+               setSelection(selEnd, -(selEnd - promptLen));
+}
+
+void PromptLineEdit::keyPressEvent(QKeyEvent * event)
+{
+       if (event->key() == Qt::Key_Backspace)
+       {
+               if (cursorPosition() <= prompt.length())
+               {
+                       event->accept();
+                       return;
+               }
+       }
+
+       QLineEdit::keyPressEvent(event);
+}
+
+QString PromptLineEdit::text(void) const
+{
+       QString s = QLineEdit::text();
+
+       return s.right(s.length() - prompt.length());
+}
+
+void PromptLineEdit::SetPrompt(QString s)
+{
+       prompt = s + ": ";
+       setText(prompt);
+}
diff --git a/src/promptlineedit.h b/src/promptlineedit.h
new file mode 100644 (file)
index 0000000..2c9b054
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef __PROMPTLINEEDIT_H__
+#define __PROMPTLINEEDIT_H__
+
+#include <QtWidgets>
+
+class PromptLineEdit: public QLineEdit
+{
+       Q_OBJECT
+
+       public:
+               PromptLineEdit(QWidget * parent = NULL);
+               ~PromptLineEdit();
+
+       public slots:
+               void HandleCursorMoved(int, int);
+               void HandleTextChanged(const QString &);
+               void HandleSelectionChanged(void);
+
+       protected:
+               void keyPressEvent(QKeyEvent *);
+
+       public:
+               QString text(void) const;
+               void SetPrompt(QString);
+
+       private:
+               QString prompt;
+};
+
+#endif // __PROMPTLINEEDIT_H__
index bef3c52f93879fa24f545b90ca9cf4cdc97ac813..0d16ff8d30f5c81772766997d20ebae3b652b434 100644 (file)
@@ -8,7 +8,7 @@
 #include "rect.h"
 #include "vector.h"
 
-enum ObjectType { OTNone = 0, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTDimension, OTSpline, OTText, OTContainer, OTCount };
+enum ObjectType { OTNone = 0, OTLine, OTCircle, OTEllipse, OTArc, OTPolygon, OTDimension, OTSpline, OTText, OTContainer, OTPolyline, OTCount };
 
 enum DimensionType { DTLinear = 0, DTLinearVert, DTLinearHorz, DTRadial, DTDiametric, DTCircumferential, DTAngular, DTLeader, DTCount };
 
@@ -37,7 +37,7 @@ const char dimName[DTCount][32] = {
        bool hitPoint[5]; \
        bool hitObject;   \
        Point p[5];       \
-       double angle[2];  \
+       double angle[3];  \
        double radius[2]; \
        double length;
 
@@ -116,10 +116,22 @@ struct Text {
 
 struct Polygon {
        OBJECT_COMMON;
+       int sides;
 
        Polygon(): type(OTPolygon), id(Global::objectID++) {}
 };
 
+struct Polyline {
+       OBJECT_COMMON;
+       VPVector objects;
+       bool closed;
+       Object * clicked;
+
+       Polyline(): type(OTPolyline), id(Global::objectID++), selected(false), hovered(false), hitObject(false), clicked(NULL) {}
+       void Add(void * obj) { objects.push_back(obj); }
+       void Add(VPVector objs) { objects.insert(objects.end(), objs.begin(), objs.end()); }
+};
+
 struct Spline {
        OBJECT_COMMON;
 
@@ -154,4 +166,3 @@ struct Container {
 };
 
 #endif // __STRUCTS_H__
-
index 57f49fbb8b1d4bc7ae1566c91cdb58586691895f..89b9148a44343b6aa7f3c00bbf8210099731f97f 100644 (file)
@@ -25,11 +25,13 @@ Vector::Vector(double xx/*= 0*/, double yy/*= 0*/, double zz/*= 0*/): x(xx), y(y
 {
 }
 
-
 Vector::Vector(Vector tail, Vector head): x(head.x - tail.x), y(head.y - tail.y), z(head.z - tail.z)
 {
 }
 
+Vector::Vector(const Vector &v): x(v.x), y(v.y), z(v.z)
+{
+}
 
 // Create vector from angle + length (2D; z is set to zero)
 void Vector::SetAngleAndLength(double angle, double length)
@@ -39,7 +41,6 @@ void Vector::SetAngleAndLength(double angle, double length)
        z = 0;
 }
 
-
 Vector Vector::operator=(Vector const v)
 {
        x = v.x, y = v.y, z = v.z;
@@ -47,19 +48,16 @@ Vector Vector::operator=(Vector const v)
        return *this;
 }
 
-
 Vector Vector::operator+(Vector const v)
 {
        return Vector(x + v.x, y + v.y, z + v.z);
 }
 
-
 Vector Vector::operator-(Vector const v)
 {
        return Vector(x - v.x, y - v.y, z - v.z);
 }
 
-
 // Unary negation
 
 Vector Vector::operator-(void)
@@ -67,7 +65,6 @@ Vector Vector::operator-(void)
        return Vector(-x, -y, -z);
 }
 
-
 // Vector x constant
 
 Vector Vector::operator*(double const v)
@@ -75,7 +72,6 @@ Vector Vector::operator*(double const v)
        return Vector(x * v, y * v, z * v);
 }
 
-
 // Vector x constant
 
 Vector Vector::operator*(float const v)
@@ -83,7 +79,6 @@ Vector Vector::operator*(float const v)
        return Vector(x * v, y * v, z * v);
 }
 
-
 // Vector / constant
 
 Vector Vector::operator/(double const v)
@@ -91,7 +86,6 @@ Vector Vector::operator/(double const v)
        return Vector(x / v, y / v, z / v);
 }
 
-
 // Vector / constant
 
 Vector Vector::operator/(float const v)
@@ -99,7 +93,6 @@ Vector Vector::operator/(float const v)
        return Vector(x / v, y / v, z / v);
 }
 
-
 // Vector (cross) product
 
 Vector Vector::operator*(Vector const v)
@@ -108,7 +101,6 @@ Vector Vector::operator*(Vector const v)
        return Vector((y * v.z) - (z * v.y), (z * v.x) - (x * v.z), (x * v.y) - (y * v.x));
 }
 
-
 // Dot product
 
 double Vector::Dot(Vector const v)
@@ -116,7 +108,6 @@ double Vector::Dot(Vector const v)
        return (x * v.x) + (y * v.y) + (z * v.z);
 }
 
-
 // Vector x constant, self assigned
 
 Vector& Vector::operator*=(double const v)
@@ -126,7 +117,6 @@ Vector& Vector::operator*=(double const v)
        return *this;
 }
 
-
 // Vector / constant, self assigned
 
 Vector& Vector::operator/=(double const v)
@@ -145,7 +135,6 @@ Vector& Vector::operator+=(Vector const v)
        return *this;
 }
 
-
 // Vector + constant, self assigned
 
 Vector& Vector::operator+=(double const v)
@@ -155,7 +144,6 @@ Vector& Vector::operator+=(double const v)
        return *this;
 }
 
-
 // Vector - vector, self assigned
 
 Vector& Vector::operator-=(Vector const v)
@@ -165,7 +153,6 @@ Vector& Vector::operator-=(Vector const v)
        return *this;
 }
 
-
 // Vector - constant, self assigned
 
 Vector& Vector::operator-=(double const v)
@@ -175,21 +162,18 @@ Vector& Vector::operator-=(double const v)
        return *this;
 }
 
-
 // Check for equality
 bool Vector::operator==(Vector const v)
 {
        return (x == v.x && y == v.y && z == v.z ? true : false);
 }
 
-
 // Check for inequality
 bool Vector::operator!=(Vector const v)
 {
        return (x != v.x || y != v.y || z != v.z ? true : false);
 }
 
-
 Vector Vector::Unit(void)
 {
        double mag = Magnitude();
@@ -202,13 +186,11 @@ Vector Vector::Unit(void)
        return Vector(x / mag, y / mag, z / mag);
 }
 
-
 double Vector::Magnitude(void)
 {
        return sqrt((x * x) + (y * y) + (z * z));
 }
 
-
 double Vector::Angle(void)
 {
        // acos returns a value between zero and TAU/2, which means we don't know
@@ -220,13 +202,11 @@ double Vector::Angle(void)
        return correctedAngle;
 }
 
-
 bool Vector::isZero(double epsilon/*= 1e-6*/)
 {
        return (fabs(x) < epsilon && fabs(y) < epsilon && fabs(z) < epsilon ? true : false);
 }
 
-
 // Class methods
 
 /*static*/ double Vector::Dot(Vector v1, Vector v2)
@@ -234,16 +214,15 @@ bool Vector::isZero(double epsilon/*= 1e-6*/)
        return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z);
 }
 
-
 /*static*/ double Vector::Magnitude(Vector v1, Vector v2)
 {
        double xx = v1.x - v2.x;
        double yy = v1.y - v2.y;
        double zz = v1.z - v2.z;
+
        return sqrt((xx * xx) + (yy * yy) + (zz * zz));
 }
 
-
 //
 // Convenience function
 //
@@ -252,7 +231,6 @@ bool Vector::isZero(double epsilon/*= 1e-6*/)
        return Vector(p1, p2).Angle();
 }
 
-
 // Returns the parameter of a point in space to this vector. If the parameter
 // is between 0 and 1, the normal of the vector to the point is on the vector.
 // Note: v1 is the tail, v2 is the head of the line (vector).
@@ -268,19 +246,19 @@ bool Vector::isZero(double epsilon/*= 1e-6*/)
        double magnitude = lineSegment.Magnitude();
        Vector pointSegment = p - tail;
        double t = lineSegment.Dot(pointSegment) / (magnitude * magnitude);
+
        return t;
 }
 
-
 // Return the 2D normal to the linesegment formed by the passed in points.
 // The normal thus calculated should rotate anti-clockwise.
 /*static*/ Vector Vector::Normal(Vector tail, Vector head)
 {
        Vector v = (head - tail).Unit();
+
        return Vector(-v.y, v.x);
 }
 
-
 /*static*/ double Vector::AngleBetween(Vector a, Vector b)
 {
        // This is done using the following formula:
@@ -295,4 +273,3 @@ bool Vector::isZero(double epsilon/*= 1e-6*/)
 
        return acos(a.Dot(b) / (a.Magnitude() * b.Magnitude()));
 }
-
index 5ef0a9242b75188812cff6aee7fa8091990023e3..67d9aa92f8e0f0d82ebb7bf2fdff4c7febe63277 100644 (file)
@@ -20,7 +20,8 @@ class Vector
 {
        public:
                Vector(double xx = 0, double yy = 0, double zz = 0);
-               Vector(Vector tail, Vector head);               // Create vector from two points
+               Vector(Vector tail, Vector head);       // Create vector from two points
+               Vector(const Vector &v);
                void SetAngleAndLength(double angle, double length);
                Vector operator=(Vector const v);
                Vector operator+(Vector const v);
@@ -40,8 +41,8 @@ class Vector
                Vector& operator-=(Vector const v);     // Vector minus Vector self-assignment
                Vector& operator-=(double const v);     // Vector minus constant self-assignment
 
-               bool operator==(Vector const v);                // Check for equality
-               bool operator!=(Vector const v);                // Check for inequality
+               bool operator==(Vector const v);        // Check for equality
+               bool operator!=(Vector const v);        // Check for inequality
 
                Vector Unit(void);
                double Magnitude(void);