From b9ab62ab3fbffbaa119a350d9cb2daa380fe1128 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Tue, 3 Nov 2009 10:54:49 -0800 Subject: [PATCH] Remove arbitrary limits on the selection size, and permit more than one point to be dragged simultaneously. So now a dragged point drags all the selected points and entities, and a dragged entity drags its points (except for circles, which drag the radius). This means that the number of forced points for the solver must now be unlimited, and it is. Also add commands to invert the selection within the active group, and to select an edge chain starting from the current selection. And redo the context menus a bit; still not great, but less cluttered and more systematic. [git-p4: depot-paths = "//depot/solvespace/": change = 2064] --- draw.cpp | 76 +++++++----- entity.cpp | 32 ++++++ exposed/lib.cpp | 13 +-- generate.cpp | 37 +++--- graphicswin.cpp | 85 ++++++++++++-- mouse.cpp | 298 +++++++++++++++++++++++++++++------------------- sketch.h | 5 + solvespace.h | 3 +- system.cpp | 6 +- textscreens.cpp | 12 +- textwin.cpp | 3 +- ui.h | 38 ++++-- wishlist.txt | 3 +- 13 files changed, 409 insertions(+), 202 deletions(-) diff --git a/draw.cpp b/draw.cpp index bad26ba..24dbebd 100644 --- a/draw.cpp +++ b/draw.cpp @@ -26,6 +26,12 @@ bool GraphicsWindow::Selection::IsStylable(void) { return false; } +bool GraphicsWindow::Selection::HasEndpoints(void) { + if(!entity.v) return false; + Entity *e = SK.GetEntity(entity); + return e->HasEndpoints(); +} + void GraphicsWindow::Selection::Clear(void) { entity.v = constraint.v = 0; emphasized = false; @@ -64,67 +70,75 @@ void GraphicsWindow::Selection::Draw(void) { } void GraphicsWindow::ClearSelection(void) { - for(int i = 0; i < MAX_SELECTED; i++) { - selection[i].Clear(); - } + selection.Clear(); SS.later.showTW = true; InvalidateGraphics(); } void GraphicsWindow::ClearNonexistentSelectionItems(void) { bool change = false; - for(int i = 0; i < MAX_SELECTED; i++) { - Selection *s = &(selection[i]); + Selection *s; + selection.ClearTags(); + for(s = selection.First(); s; s = selection.NextAfter(s)) { if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) { - s->constraint.v = 0; + s->tag = 1; change = true; } if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) { - s->entity.v = 0; + s->tag = 1; change = true; } } + selection.RemoveTagged(); if(change) InvalidateGraphics(); } //----------------------------------------------------------------------------- -// Toggle the selection state of the hovered item: if it was selected then +// Toggle the selection state of the indicated item: if it was selected then // un-select it, and if it wasn't then select it. //----------------------------------------------------------------------------- -void GraphicsWindow::ToggleSelectionStateOfHovered(void) { - if(hover.IsEmpty()) return; +void GraphicsWindow::ToggleSelectionStateOf(hEntity he) { + Selection stog; + ZERO(&stog); + stog.entity = he; + ToggleSelectionStateOf(&stog); +} +void GraphicsWindow::ToggleSelectionStateOf(Selection *stog) { + if(stog->IsEmpty()) return; + + Selection *s; // If an item was selected, then we just un-select it. - int i; - for(i = 0; i < MAX_SELECTED; i++) { - if(selection[i].Equals(&hover)) { - selection[i].Clear(); + bool wasSelected = false; + selection.ClearTags(); + for(s = selection.First(); s; s = selection.NextAfter(s)) { + if(s->Equals(stog)) { + s->tag = 1; + wasSelected = true; break; } } - if(i != MAX_SELECTED) return; + selection.RemoveTagged(); + if(wasSelected) return; // So it's not selected, so we should select it. - if(hover.entity.v != 0 && SK.GetEntity(hover.entity)->IsFace()) { + if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) { // In the interest of speed for the triangle drawing code, // only two faces may be selected at a time. int c = 0; - for(i = 0; i < MAX_SELECTED; i++) { - hEntity he = selection[i].entity; + selection.ClearTags(); + for(s = selection.First(); s; s = selection.NextAfter(s)) { + hEntity he = s->entity; if(he.v != 0 && SK.GetEntity(he)->IsFace()) { c++; - if(c >= 2) selection[i].Clear(); + if(c >= 2) s->tag = 1; } } + selection.RemoveTagged(); } - for(i = 0; i < MAX_SELECTED; i++) { - if(selection[i].IsEmpty()) { - selection[i] = hover; - break; - } - } + selection.Add(stog); } //----------------------------------------------------------------------------- @@ -135,8 +149,8 @@ void GraphicsWindow::ToggleSelectionStateOfHovered(void) { void GraphicsWindow::GroupSelection(void) { memset(&gs, 0, sizeof(gs)); int i; - for(i = 0; i < MAX_SELECTED; i++) { - Selection *s = &(selection[i]); + for(i = 0; i < selection.n && i < MAX_SELECTED; i++) { + Selection *s = &(selection.elem[i]); if(s->entity.v) { (gs.n)++; @@ -167,6 +181,10 @@ void GraphicsWindow::GroupSelection(void) { gs.face[(gs.faces)++] = s->entity; } + if(e->HasEndpoints()) { + (gs.withEndpoints)++; + } + // And some aux counts too switch(e->type) { case Entity::WORKPLANE: (gs.workplanes)++; break; @@ -533,8 +551,8 @@ nogrid:; // And finally draw the selection, same mechanism. glxLockColorTo(Style::Color(Style::SELECTED)); - for(i = 0; i < MAX_SELECTED; i++) { - selection[i].Draw(); + for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) { + s->Draw(); } glxUnlockColor(); diff --git a/entity.cpp b/entity.cpp index 4624e75..03a7b1c 100644 --- a/entity.cpp +++ b/entity.cpp @@ -157,6 +157,10 @@ void EntityBase::WorkplaneGetPlaneExprs(ExprVector *n, Expr **dn) { } } +bool EntityBase::IsDistance(void) { + return (type == DISTANCE) || + (type == DISTANCE_N_COPY); +} double EntityBase::DistanceGetNum(void) { if(type == DISTANCE) { return SK.GetParam(param[0])->val; @@ -679,6 +683,34 @@ Vector EntityBase::FaceGetPointNum(void) { return r; } +bool EntityBase::HasEndpoints(void) { + return (type == LINE_SEGMENT) || + (type == CUBIC) || + (type == ARC_OF_CIRCLE); +} +Vector EntityBase::EndpointStart() { + if(type == LINE_SEGMENT) { + return SK.GetEntity(point[0])->PointGetNum(); + } else if(type == CUBIC) { + return CubicGetStartNum(); + } else if(type == ARC_OF_CIRCLE) { + return SK.GetEntity(point[1])->PointGetNum(); + } else { + oops(); + } +} +Vector EntityBase::EndpointFinish() { + if(type == LINE_SEGMENT) { + return SK.GetEntity(point[1])->PointGetNum(); + } else if(type == CUBIC) { + return CubicGetFinishNum(); + } else if(type == ARC_OF_CIRCLE) { + return SK.GetEntity(point[2])->PointGetNum(); + } else { + oops(); + } +} + void EntityBase::AddEq(IdList *l, Expr *expr, int index) { Equation eq; eq.e = expr; diff --git a/exposed/lib.cpp b/exposed/lib.cpp index 82e7456..8b3206b 100644 --- a/exposed/lib.cpp +++ b/exposed/lib.cpp @@ -181,14 +181,12 @@ default: dbp("bad constraint type %d", sc->type); return; SK.constraint.Add(&c); } - if(System::MAX_DRAGGED < 4) oops(); - for(i = 0; i < System::MAX_DRAGGED; i++) { - SYS.dragged[i].v = 0; + for(i = 0; i < arraylen(ssys->dragged); i++) { + if(ssys->dragged[i]) { + hParam hp = { ssys->dragged[i] }; + SYS.dragged.Add(&hp); + } } - SYS.dragged[0].v = ssys->dragged[0]; - SYS.dragged[1].v = ssys->dragged[1]; - SYS.dragged[2].v = ssys->dragged[2]; - SYS.dragged[3].v = ssys->dragged[3]; Group g; ZERO(&g); @@ -240,6 +238,7 @@ default: dbp("bad constraint type %d", sc->type); return; SYS.param.Clear(); SYS.entity.Clear(); SYS.eq.Clear(); + SYS.dragged.Clear(); SK.param.Clear(); SK.entity.Clear(); diff --git a/generate.cpp b/generate.cpp index c86a773..8fa6eed 100644 --- a/generate.cpp +++ b/generate.cpp @@ -381,28 +381,33 @@ void SolveSpace::ForceReferences(void) { } void SolveSpace::MarkDraggedParams(void) { - int i; - for(i = 0; i < System::MAX_DRAGGED; i++) { - sys.dragged[i] = Param::NO_PARAM; - } + sys.dragged.Clear(); + + for(int i = -1; i < SS.GW.pending.points.n; i++) { + hEntity hp; + if(i == -1) { + hp = SS.GW.pending.point; + } else { + hp = SS.GW.pending.points.elem[i]; + } + if(!hp.v) continue; - if(SS.GW.pending.point.v) { // The pending point could be one in a group that has not yet // been processed, in which case the lookup will fail; but // that's not an error. - Entity *pt = SK.entity.FindByIdNoOops(SS.GW.pending.point); + Entity *pt = SK.entity.FindByIdNoOops(hp); if(pt) { switch(pt->type) { case Entity::POINT_N_TRANS: case Entity::POINT_IN_3D: - sys.dragged[0] = pt->param[0]; - sys.dragged[1] = pt->param[1]; - sys.dragged[2] = pt->param[2]; + sys.dragged.Add(&(pt->param[0])); + sys.dragged.Add(&(pt->param[1])); + sys.dragged.Add(&(pt->param[2])); break; case Entity::POINT_IN_2D: - sys.dragged[0] = pt->param[0]; - sys.dragged[1] = pt->param[1]; + sys.dragged.Add(&(pt->param[0])); + sys.dragged.Add(&(pt->param[1])); break; } } @@ -413,7 +418,7 @@ void SolveSpace::MarkDraggedParams(void) { Entity *dist = SK.GetEntity(circ->distance); switch(dist->type) { case Entity::DISTANCE: - sys.dragged[0] = dist->param[0]; + sys.dragged.Add(&(dist->param[0])); break; } } @@ -423,10 +428,10 @@ void SolveSpace::MarkDraggedParams(void) { if(norm) { switch(norm->type) { case Entity::NORMAL_IN_3D: - sys.dragged[0] = norm->param[0]; - sys.dragged[1] = norm->param[1]; - sys.dragged[2] = norm->param[2]; - sys.dragged[3] = norm->param[3]; + sys.dragged.Add(&(norm->param[0])); + sys.dragged.Add(&(norm->param[1])); + sys.dragged.Add(&(norm->param[2])); + sys.dragged.Add(&(norm->param[3])); break; // other types are locked, so not draggable } diff --git a/graphicswin.cpp b/graphicswin.cpp index b7ff561..e34ae9b 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -35,8 +35,14 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, NULL, 0, NULL }, { 1, "Snap Selection to &Grid\t.", MNU_SNAP_TO_GRID, '.', mEdit }, { 1, "Rotate Imported &90°\t9", MNU_ROTATE_90, '9', mEdit }, +{ 1, NULL, 0, NULL }, +{ 1, "Cu&t\tCtrl+X", MNU_CUT, 'X'|C, mEdit }, +{ 1, "&Copy\tCtrl+C", MNU_COPY, 'C'|C, mEdit }, +{ 1, "&Paste\tCtrl+V", MNU_PASTE, 'V'|C, mEdit }, { 1, "&Delete\tDel", MNU_DELETE, 127, mEdit }, { 1, NULL, 0, NULL }, +{ 1, "Select Edge Cha&in\tCtrl+I", MNU_SELECT_CHAIN, 'I'|C, mEdit }, +{ 1, "Invert &Selection\tCtrl+A", MNU_INVERT_SEL, 'A'|C, mEdit }, { 1, "&Unselect All\tEsc", MNU_UNSELECT_ALL, 27, mEdit }, { 0, "&View", 0, NULL }, @@ -618,14 +624,75 @@ void GraphicsWindow::MenuEdit(int id) { SS.nakedEdges.Clear(); break; + case MNU_INVERT_SEL: { + Entity *e; + for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { + if(e->group.v != SS.GW.activeGroup.v) continue; + if(e->IsFace() || e->IsDistance()) continue; + + SS.GW.ToggleSelectionStateOf(e->h); + } + InvalidateGraphics(); + SS.later.showTW = true; + break; + } + + case MNU_SELECT_CHAIN: { + Entity *e; + int newlySelected = 0; + bool didSomething; + do { + didSomething = false; + for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { + if(e->group.v != SS.GW.activeGroup.v) continue; + if(!e->HasEndpoints()) continue; + + Vector st = e->EndpointStart(), + fi = e->EndpointFinish(); + + bool onChain = false, alreadySelected = false; + List *ls = &(SS.GW.selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + if(!s->entity.v) continue; + if(s->entity.v == e->h.v) { + alreadySelected = true; + continue; + } + Entity *se = SK.GetEntity(s->entity); + if(!se->HasEndpoints()) continue; + + Vector sst = se->EndpointStart(), + sfi = se->EndpointFinish(); + + if(sst.Equals(st) || sst.Equals(fi) || + sfi.Equals(st) || sfi.Equals(fi)) + { + onChain = true; + } + } + if(onChain && !alreadySelected) { + SS.GW.ToggleSelectionStateOf(e->h); + newlySelected++; + didSomething = true; + } + } + } while(didSomething); + if(newlySelected == 0) { + Error("No entities share endpoints with the selected " + "entities."); + } + InvalidateGraphics(); + SS.later.showTW = true; + break; + } + case MNU_DELETE: { SS.UndoRemember(); - int i; SK.request.ClearTags(); SK.constraint.ClearTags(); - for(i = 0; i < MAX_SELECTED; i++) { - Selection *s = &(SS.GW.selection[i]); + List *ls = &(SS.GW.selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { hRequest r; r.v = 0; if(s->entity.v && s->entity.isFromRequest()) { r = s->entity.request(); @@ -702,14 +769,14 @@ void GraphicsWindow::MenuEdit(int id) { Vector p = ep->PointGetNum(); ep->PointForceTo(SS.GW.SnapToGrid(p)); - - // Regenerate, with this point marked as dragged so that it - // gets placed as close as possible to our snap - SS.GW.pending.point = hp; + SS.GW.pending.points.Add(&hp); SS.MarkGroupDirty(ep->group); - SS.GenerateAll(); - SS.GW.pending.point = Entity::NO_ENTITY; } + // Regenerate, with these points marked as dragged so that they + // get placed as close as possible to our snap grid. + SS.GenerateAll(); + SS.GW.ClearPending(); + for(i = 0; i < SS.GW.gs.constraints; i++) { Constraint *c = SK.GetConstraint(SS.GW.gs.constraint[i]); c->disp.offset = SS.GW.SnapToGrid(c->disp.offset); diff --git a/mouse.cpp b/mouse.cpp index 86644bd..b45d139 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -13,10 +13,64 @@ void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) { *pos = pos->Plus(projRight.ScaledBy((mx - orig.mouse.x)/scale)); *pos = pos->Plus(projUp.ScaledBy((my - orig.mouse.y)/scale)); +} - orig.mouse.x = mx; - orig.mouse.y = my; - InvalidateGraphics(); +void GraphicsWindow::AddPointToDraggedList(hEntity hp) { + Entity *p = SK.GetEntity(hp); + // If an entity and its points are both selected, then its points could + // end up in the list twice. This would be bad, because it would move + // twice as far as the mouse pointer... + List *lhe = &(pending.points); + for(hEntity *hee = lhe->First(); hee; hee = lhe->NextAfter(hee)) { + if(hee->v == hp.v) { + // Exact same point. + return; + } + Entity *pe = SK.GetEntity(*hee); + if(pe->type == p->type && + pe->type != Entity::POINT_IN_2D && + pe->type != Entity::POINT_IN_3D && + pe->group.v == p->group.v) + { + // Transform-type point, from the same group. So it handles the + // same unknowns. + return; + } + } + pending.points.Add(&hp); +} + +void GraphicsWindow::StartDraggingByEntity(hEntity he) { + Entity *e = SK.GetEntity(he); + if(e->IsPoint()) { + AddPointToDraggedList(e->h); + } else if(e->type == Entity::LINE_SEGMENT || + e->type == Entity::ARC_OF_CIRCLE || + e->type == Entity::CUBIC || + e->type == Entity::CUBIC_PERIODIC || + e->type == Entity::CIRCLE || + e->type == Entity::TTF_TEXT) + { + int pts; + EntReqTable::GetEntityInfo(e->type, e->extraPoints, + NULL, &pts, NULL, NULL); + for(int i = 0; i < pts; i++) { + AddPointToDraggedList(e->point[i]); + } + } +} + +void GraphicsWindow::StartDraggingBySelection(void) { + List *ls = &(selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + if(!s->entity.v) continue; + + StartDraggingByEntity(s->entity); + } + // The user might select a point, and then click it again to start + // dragging; but the point just got unselected by that click. So drag + // the hovered item too, and they'll always have it. + if(hover.entity.v) StartDraggingByEntity(hover.entity); } void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, @@ -37,7 +91,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, } } - if(!leftDown && pending.operation == DRAGGING_POINT) { + if(!leftDown && pending.operation == DRAGGING_POINTS) { ClearPending(); } @@ -96,12 +150,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, if(leftDown && dm > 3) { if(hover.entity.v) { Entity *e = SK.GetEntity(hover.entity); - if(e->IsPoint()) { - // Start dragging this point. - ClearSelection(); - pending.point = hover.entity; - pending.operation = DRAGGING_POINT; - } else if(e->type == Entity::CIRCLE) { + if(e->type == Entity::CIRCLE) { // Drag the radius. ClearSelection(); pending.circle = hover.entity; @@ -110,6 +159,11 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, ClearSelection(); pending.normal = hover.entity; pending.operation = DRAGGING_NORMAL; + } else { + StartDraggingBySelection(); + ClearSelection(); + hover.Clear(); + pending.operation = DRAGGING_POINTS; } } else if(hover.constraint.v && SK.GetConstraint(hover.constraint)->HasLabel()) @@ -149,26 +203,28 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, case DRAGGING_CONSTRAINT: { Constraint *c = SK.constraint.FindById(pending.constraint); UpdateDraggedNum(&(c->disp.offset), x, y); + orig.mouse = mp; + InvalidateGraphics(); break; } - case DRAGGING_NEW_LINE_POINT: - HitTestMakeSelection(mp); - // and fall through - case DRAGGING_NEW_POINT: - case DRAGGING_POINT: { - Entity *p = SK.GetEntity(pending.point); - if((p->type == Entity::POINT_N_ROT_TRANS) && - (shiftDown || ctrlDown)) - { - // These points also come with a rotation, which the user can - // edit by pressing shift or control. - Quaternion q = p->PointGetQuaternion(); - Vector p3 = p->PointGetNum(); - Point2d p2 = ProjectPoint(p3); - Vector u = q.RotationU(), v = q.RotationV(); + case DRAGGING_NEW_LINE_POINT: + case DRAGGING_NEW_POINT: + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + SS.MarkGroupDirtyByEntity(pending.point); + orig.mouse = mp; + InvalidateGraphics(); + break; + + case DRAGGING_POINTS: + if(shiftDown || ctrlDown) { + // Edit the rotation associated with a POINT_N_ROT_TRANS, + // either within (ctrlDown) or out of (shiftDown) the plane + // of the screen. So first get the rotation to apply, in qt. + Quaternion qt; if(ctrlDown) { - double d = mp.DistanceTo(p2); + double d = mp.DistanceTo(orig.mouseOnButtonDown); if(d < 25) { // Don't start dragging the position about the normal // until we're a little ways out, to get a reasonable @@ -176,34 +232,47 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, orig.mouse = mp; break; } - double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); - theta -= atan2(y-p2.y, x-p2.x); + double theta = atan2(orig.mouse.y-orig.mouseOnButtonDown.y, + orig.mouse.x-orig.mouseOnButtonDown.x); + theta -= atan2(y-orig.mouseOnButtonDown.y, + x-orig.mouseOnButtonDown.x); Vector gn = projRight.Cross(projUp); - u = u.RotatedAbout(gn, -theta); - v = v.RotatedAbout(gn, -theta); + qt = Quaternion::From(gn, -theta); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); double s = 0.3*(PI/180); // degrees per pixel - u = u.RotatedAbout(projUp, -s*dx); - u = u.RotatedAbout(projRight, s*dy); - v = v.RotatedAbout(projUp, -s*dx); - v = v.RotatedAbout(projRight, s*dy); + qt = Quaternion::From(projUp, -s*dx).Times( + Quaternion::From(projRight, s*dy)); } - q = Quaternion::From(u, v); - p->PointForceQuaternionTo(q); - // Let's rotate about the selected point; so fix up the - // translation so that that point didn't move. - p->PointForceTo(p3); orig.mouse = mp; + + // Now apply this rotation to the points being dragged. + List *lhe = &(pending.points); + for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { + Entity *e = SK.GetEntity(*he); + if(e->type != Entity::POINT_N_ROT_TRANS) continue; + + Quaternion q = e->PointGetQuaternion(); + Vector p = e->PointGetNum(); + q = qt.Times(q); + e->PointForceQuaternionTo(q); + // Let's rotate about the selected point; so fix up the + // translation so that that point didn't move. + e->PointForceTo(p); + SS.MarkGroupDirtyByEntity(e->h); + } } else { - UpdateDraggedPoint(pending.point, x, y); - HitTestMakeSelection(mp); + List *lhe = &(pending.points); + for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { + UpdateDraggedPoint(*he, x, y); + SS.MarkGroupDirtyByEntity(*he); + } + orig.mouse = mp; } - SS.MarkGroupDirtyByEntity(pending.point); break; - } + case DRAGGING_NEW_CUBIC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); @@ -228,6 +297,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SK.GetEntity(hr.entity(3+i))->PointForceTo(pnm1); } + orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } @@ -242,6 +312,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SK.GetEntity(hr.entity(1))->PointForceTo(center); + orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } @@ -297,7 +368,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, } void GraphicsWindow::ClearPending(void) { - memset(&pending, 0, sizeof(pending)); + pending.points.Clear(); + ZERO(&pending); SS.later.showTW = true; } @@ -359,105 +431,99 @@ void GraphicsWindow::MouseRightUp(double x, double y) { // or on the hovered item. In the latter case we can fudge things by just // selecting the hovered item, and then applying our operation to the // selection. - bool toggleForStyles = false, - toggleForGroupInfo = false, - toggleForDelete = false, - toggleForStyleInfo = false; - - if(!hover.IsEmpty()) { - AddContextMenuItem("Toggle Hovered Item Selection", - CMNU_TOGGLE_SELECTION); + bool selEmpty = false; + if(gs.n == 0 && gs.constraints == 0) { + selEmpty = true; } - if(gs.stylables > 0) { - ContextMenuListStyles(); - AddContextMenuItem("Assign Selection to Style", CONTEXT_SUBMENU); - } else if(gs.n == 0 && gs.constraints == 0 && hover.IsStylable()) { - ContextMenuListStyles(); - AddContextMenuItem("Assign Hovered Item to Style", CONTEXT_SUBMENU); - toggleForStyles = true; - } - - if(gs.n + gs.constraints == 1) { - AddContextMenuItem("Group Info for Selected Item", CMNU_GROUP_INFO); - } else if(!hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { - AddContextMenuItem("Group Info for Hovered Item", CMNU_GROUP_INFO); - toggleForGroupInfo = true; - } - - if(gs.n + gs.constraints == 1 && gs.stylables == 1) { - AddContextMenuItem("Style Info for Selected Item", CMNU_STYLE_INFO); - } else if(hover.IsStylable() && gs.n == 0 && gs.constraints == 0) { - AddContextMenuItem("Style Info for Hovered Item", CMNU_STYLE_INFO); - toggleForStyleInfo = true; - } - - if(hover.constraint.v && gs.n == 0 && gs.constraints == 0) { - Constraint *c = SK.GetConstraint(hover.constraint); - if(c->HasLabel() && c->type != Constraint::COMMENT) { - AddContextMenuItem("Toggle Reference Dimension", - CMNU_REFERENCE_DIM); + if(selEmpty) { + if(hover.IsStylable()) { + ContextMenuListStyles(); + AddContextMenuItem("Hovered: Assign to Style", CONTEXT_SUBMENU); } - if(c->type == Constraint::ANGLE || - c->type == Constraint::EQUAL_ANGLE) + if(!hover.IsEmpty()) { + AddContextMenuItem("Hovered: Group Info", CMNU_GROUP_INFO); + } + if(hover.IsStylable()) { + AddContextMenuItem("Hovered: Style Info", CMNU_STYLE_INFO); + } + if(hover.constraint.v) { + Constraint *c = SK.GetConstraint(hover.constraint); + if(c->HasLabel() && c->type != Constraint::COMMENT) { + AddContextMenuItem("Hovered: Toggle Reference Dimension", + CMNU_REFERENCE_DIM); + } + if(c->type == Constraint::ANGLE || + c->type == Constraint::EQUAL_ANGLE) + { + AddContextMenuItem("Hovered: Other Supplementary Angle", + CMNU_OTHER_ANGLE); + } + } + if(hover.HasEndpoints()) { + AddContextMenuItem("Hovered: Select Edge Chain", CMNU_SELECT_CHAIN); + } + if((hover.constraint.v && + SK.GetConstraint(hover.constraint)->type == Constraint::COMMENT) || + (hover.entity.v && + SK.GetEntity(hover.entity)->IsPoint())) { - AddContextMenuItem("Other Supplementary Angle", - CMNU_OTHER_ANGLE); + AddContextMenuItem("Hovered: Snap to Grid", CMNU_SNAP_TO_GRID); + } + if(!hover.IsEmpty()) { + AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + AddContextMenuItem("Delete Hovered Item", CMNU_DELETE_SEL); + } + } else { + if(gs.stylables > 0) { + ContextMenuListStyles(); + AddContextMenuItem("Selected: Assign to Style", CONTEXT_SUBMENU); + } + if(gs.n + gs.constraints == 1) { + AddContextMenuItem("Selected: Group Info", CMNU_GROUP_INFO); + } + if(gs.n + gs.constraints == 1 && gs.stylables == 1) { + AddContextMenuItem("Selected: Style Info", CMNU_STYLE_INFO); + } + if(gs.withEndpoints > 0) { + AddContextMenuItem("Selected: Select Edge Chain", + CMNU_SELECT_CHAIN); } - } - - if(gs.n == 0 && gs.constraints == 0 && - (hover.constraint.v && - SK.GetConstraint(hover.constraint)->type == Constraint::COMMENT) || - (hover.entity.v && - SK.GetEntity(hover.entity)->IsPoint())) - { - AddContextMenuItem("Snap to Grid", CMNU_SNAP_TO_GRID); - } - - if(gs.n > 0 || gs.constraints > 0) { AddContextMenuItem(NULL, CONTEXT_SEPARATOR); AddContextMenuItem("Delete Selection", CMNU_DELETE_SEL); AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); - } else if(!hover.IsEmpty()) { - AddContextMenuItem(NULL, CONTEXT_SEPARATOR); - AddContextMenuItem("Delete Hovered Item", CMNU_DELETE_SEL); - toggleForDelete = true; } int ret = ShowContextMenu(); + if(ret != 0 && selEmpty) { + ToggleSelectionStateOf(&hover); + } switch(ret) { - case CMNU_TOGGLE_SELECTION: - ToggleSelectionStateOfHovered(); - break; - case CMNU_UNSELECT_ALL: MenuEdit(MNU_UNSELECT_ALL); break; + case CMNU_SELECT_CHAIN: + MenuEdit(MNU_SELECT_CHAIN); + break; + case CMNU_DELETE_SEL: - if(toggleForDelete) ToggleSelectionStateOfHovered(); MenuEdit(MNU_DELETE); break; case CMNU_REFERENCE_DIM: - ToggleSelectionStateOfHovered(); Constraint::MenuConstrain(MNU_REFERENCE); break; case CMNU_OTHER_ANGLE: - ToggleSelectionStateOfHovered(); Constraint::MenuConstrain(MNU_OTHER_ANGLE); break; case CMNU_SNAP_TO_GRID: - ToggleSelectionStateOfHovered(); MenuEdit(MNU_SNAP_TO_GRID); break; case CMNU_GROUP_INFO: { - if(toggleForGroupInfo) ToggleSelectionStateOfHovered(); - hGroup hg; GroupSelection(); if(gs.entities == 1) { @@ -478,8 +544,6 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } case CMNU_STYLE_INFO: { - if(toggleForStyleInfo) ToggleSelectionStateOfHovered(); - hStyle hs; GroupSelection(); if(gs.entities == 1) { @@ -501,23 +565,20 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } case CMNU_NEW_CUSTOM_STYLE: { - if(toggleForStyles) ToggleSelectionStateOfHovered(); DWORD v = Style::CreateCustomStyle(); Style::AssignSelectionToStyle(v); break; } case CMNU_NO_STYLE: - if(toggleForStyles) ToggleSelectionStateOfHovered(); Style::AssignSelectionToStyle(0); break; default: if(ret >= CMNU_FIRST_STYLE) { - if(toggleForStyles) ToggleSelectionStateOfHovered(); Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); } - // otherwise it was probably cancelled, so do nothing + // otherwise it was cancelled, so do nothing break; } @@ -587,6 +648,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { MouseMoved(mx, my, false, false, false, false, false); orig.mouse.x = mx; orig.mouse.y = my; + orig.mouseOnButtonDown = orig.mouse; // The current mouse location Vector v = offset.ScaledBy(-1); @@ -837,7 +899,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { case 0: default: ClearPending(); - ToggleSelectionStateOfHovered(); + ToggleSelectionStateOf(&hover); break; } @@ -847,7 +909,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { void GraphicsWindow::MouseLeftUp(double mx, double my) { switch(pending.operation) { - case DRAGGING_POINT: + case DRAGGING_POINTS: case DRAGGING_CONSTRAINT: case DRAGGING_NORMAL: case DRAGGING_RADIUS: diff --git a/sketch.h b/sketch.h index 80f3112..30b9a0c 100644 --- a/sketch.h +++ b/sketch.h @@ -356,6 +356,7 @@ public: Vector VectorGetRefPoint(void); // For distances + bool IsDistance(void); double DistanceGetNum(void); Expr *DistanceGetExpr(void); void DistanceForceTo(double v); @@ -401,6 +402,10 @@ public: ExprVector CubicGetStartTangentExprs(void); ExprVector CubicGetFinishTangentExprs(void); + bool HasEndpoints(void); + Vector EndpointStart(); + Vector EndpointFinish(); + void AddEq(IdList *l, Expr *expr, int index); void GenerateEquations(IdList *l); }; diff --git a/solvespace.h b/solvespace.h index 7c6f209..e6d82f3 100644 --- a/solvespace.h +++ b/solvespace.h @@ -238,7 +238,6 @@ bool StringEndsIn(char *str, char *ending); class System { public: static const int MAX_UNKNOWNS = 1024; - static const int MAX_DRAGGED = 4; EntityList entity; ParamList param; @@ -246,7 +245,7 @@ public: // A list of parameters that are being dragged; these are the ones that // we should put as close as possible to their initial positions. - hParam dragged[MAX_DRAGGED]; + List dragged; // In general, the tag indicates the subsys that a variable/equation // has been assigned to; these are exceptions for variables: diff --git a/system.cpp b/system.cpp index 38a7b1b..aa18dd3 100644 --- a/system.cpp +++ b/system.cpp @@ -61,9 +61,9 @@ void System::EvalJacobian(void) { } bool System::IsDragged(hParam p) { - int i; - for(i = 0; i < MAX_DRAGGED; i++) { - if(p.v == dragged[i].v) return true; + hParam *pp; + for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) { + if(p.v == pp->v) return true; } return false; } diff --git a/textscreens.cpp b/textscreens.cpp index e5f5015..991d7de 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -196,12 +196,18 @@ void TextWindow::ScreenHoverRequest(int link, DWORD v) { } void TextWindow::ScreenSelectConstraint(int link, DWORD v) { SS.GW.ClearSelection(); - SS.GW.selection[0].constraint.v = v; + GraphicsWindow::Selection sel; + ZERO(&sel); + sel.constraint.v = v; + SS.GW.selection.Add(&sel); } void TextWindow::ScreenSelectRequest(int link, DWORD v) { - hRequest hr = { v }; SS.GW.ClearSelection(); - SS.GW.selection[0].entity = hr.entity(0); + GraphicsWindow::Selection sel; + ZERO(&sel); + hRequest hr = { v }; + sel.entity = hr.entity(0); + SS.GW.selection.Add(&sel); } void TextWindow::ScreenChangeGroupOption(int link, DWORD v) { diff --git a/textwin.cpp b/textwin.cpp index ff223c4..8ea289f 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -522,7 +522,8 @@ void TextWindow::DescribeSelection(void) { } else if(gs.n == 0) { Printf(true, "%FtSELECTED:%E comment text"); } else { - Printf(true, "%FtSELECTED:%E %d item%s", gs.n, gs.n == 1 ? "" : "s"); + int n = SS.GW.selection.n; + Printf(true, "%FtSELECTED:%E %d item%s", n, n == 1 ? "" : "s"); } if(shown.screen == SCREEN_STYLE_INFO && diff --git a/ui.h b/ui.h index 3cc96cf..61835a6 100644 --- a/ui.h +++ b/ui.h @@ -236,7 +236,12 @@ public: // Edit MNU_UNDO, MNU_REDO, + MNU_CUT, + MNU_COPY, + MNU_PASTE, MNU_DELETE, + MNU_SELECT_CHAIN, + MNU_INVERT_SEL, MNU_SNAP_TO_GRID, MNU_ROTATE_90, MNU_UNSELECT_ALL, @@ -321,6 +326,7 @@ public: Vector projRight; Vector projUp; Point2d mouse; + Point2d mouseOnButtonDown; bool startedMoving; } orig; @@ -357,7 +363,7 @@ public: // Operations that must be completed by doing something with the mouse // are noted here. These occupy the same space as the menu ids. static const int FIRST_PENDING = 0x0f000000; - static const int DRAGGING_POINT = 0x0f000000; + static const int DRAGGING_POINTS = 0x0f000000; static const int DRAGGING_NEW_POINT = 0x0f000001; static const int DRAGGING_NEW_LINE_POINT = 0x0f000002; static const int DRAGGING_NEW_CUBIC_POINT = 0x0f000003; @@ -367,14 +373,15 @@ public: static const int DRAGGING_NORMAL = 0x0f000007; static const int DRAGGING_NEW_RADIUS = 0x0f000008; struct { - int operation; + int operation; - hEntity point; - hEntity circle; - hEntity normal; - hConstraint constraint; + hEntity point; + List points; + hEntity circle; + hEntity normal; + hConstraint constraint; - char *description; + char *description; } pending; void ClearPending(void); // The constraint that is being edited with the on-screen textbox. @@ -399,9 +406,10 @@ public: // The current selection. class Selection { public: + int tag; + hEntity entity; hConstraint constraint; - bool emphasized; void Draw(void); @@ -410,13 +418,14 @@ public: bool IsEmpty(void); bool Equals(Selection *b); bool IsStylable(void); + bool HasEndpoints(void); }; Selection hover; - static const int MAX_SELECTED = 32; - Selection selection[MAX_SELECTED]; + List selection; void HitTestMakeSelection(Point2d mp); void ClearSelection(void); void ClearNonexistentSelectionItems(void); + static const int MAX_SELECTED = 32; struct { hEntity point[MAX_SELECTED]; hEntity entity[MAX_SELECTED]; @@ -437,13 +446,14 @@ public: int constraints; int stylables; int comments; + int withEndpoints; int n; } gs; void GroupSelection(void); - void ToggleSelectionStateOfHovered(void); + void ToggleSelectionStateOf(hEntity he); + void ToggleSelectionStateOf(Selection *s); void ClearSuper(void); - static const int CMNU_TOGGLE_SELECTION = 0x100; static const int CMNU_UNSELECT_ALL = 0x101; static const int CMNU_DELETE_SEL = 0x102; static const int CMNU_NEW_CUSTOM_STYLE = 0x103; @@ -453,6 +463,7 @@ public: static const int CMNU_OTHER_ANGLE = 0x107; static const int CMNU_STYLE_INFO = 0x108; static const int CMNU_SNAP_TO_GRID = 0x109; + static const int CMNU_SELECT_CHAIN = 0x10a; static const int CMNU_FIRST_STYLE = 0x40000000; void ContextMenuListStyles(void); @@ -481,6 +492,9 @@ public: bool showSnapGrid; + void AddPointToDraggedList(hEntity hp); + void StartDraggingByEntity(hEntity he); + void StartDraggingBySelection(void); void UpdateDraggedNum(Vector *pos, double mx, double my); void UpdateDraggedPoint(hEntity hp, double mx, double my); diff --git a/wishlist.txt b/wishlist.txt index 7a9edd0..a0ac937 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,5 @@ -multi-drag -select loop, all in group, others +marquee selection copy and paste background image associative entities from solid model, as a special group