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