void GraphicsWindow::MouseRightUp(double x, double y) { SS.extraLine.draw = false; InvalidateGraphics(); // Don't show a context menu if the user is right-clicking the toolbar, // or if they are finishing a pan. if(ToolbarMouseMoved((int)x, (int)y)) return; if(orig.startedMoving) return; if(context.active) return; if(pending.operation == DRAGGING_NEW_LINE_POINT || pending.operation == DRAGGING_NEW_CUBIC_POINT) { // Special case; use a right click to stop drawing lines, since // a left click would draw another one. This is quicker and more // intuitive than hitting escape. Likewise for new cubic segments. ClearPending(); return; } context.active = true; if(!hover.IsEmpty()) { MakeSelected(&hover); SS.ScheduleShowTW(); } GroupSelection(); bool itemsSelected = (gs.n > 0 || gs.constraints > 0); if(itemsSelected) { if(gs.stylables > 0) { ContextMenuListStyles(); AddContextMenuItem("Assign to Style", CONTEXT_SUBMENU); } if(gs.n + gs.constraints == 1) { AddContextMenuItem("Group Info", CMNU_GROUP_INFO); } if(gs.n + gs.constraints == 1 && gs.stylables == 1) { AddContextMenuItem("Style Info", CMNU_STYLE_INFO); } if(gs.withEndpoints > 0) { AddContextMenuItem("Select Edge Chain", CMNU_SELECT_CHAIN); } if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != Constraint::COMMENT) { AddContextMenuItem("Toggle Reference Dimension", CMNU_REFERENCE_DIM); } if(c->type == Constraint::ANGLE || c->type == Constraint::EQUAL_ANGLE) { AddContextMenuItem("Other Supplementary Angle", CMNU_OTHER_ANGLE); } } if(gs.comments > 0 || gs.points > 0) { AddContextMenuItem("Snap to Grid", CMNU_SNAP_TO_GRID); } if(gs.points == 1) { Entity *p = SK.GetEntity(gs.point[0]); Constraint *c; IdList<Constraint,hConstraint> *lc = &(SK.constraint); for(c = lc->First(); c; c = lc->NextAfter(c)) { if(c->type != Constraint::POINTS_COINCIDENT) continue; if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { break; } } if(c) { AddContextMenuItem("Delete Point-Coincident Constraint", CMNU_DEL_COINCIDENT); } } AddContextMenuItem(NULL, CONTEXT_SEPARATOR); if(LockedInWorkplane()) { AddContextMenuItem("Cut", CMNU_CUT_SEL); AddContextMenuItem("Copy", CMNU_COPY_SEL); } } if(SS.clipboard.r.n > 0 && LockedInWorkplane()) { AddContextMenuItem("Paste", CMNU_PASTE_SEL); } if(itemsSelected) { AddContextMenuItem("Delete", CMNU_DELETE_SEL); AddContextMenuItem(NULL, CONTEXT_SEPARATOR); AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); } // If only one item is selected, then it must be the one that we just // selected from the hovered item; in which case unselect all and hovered // are equivalent. if(!hover.IsEmpty() && selection.n > 1) { AddContextMenuItem("Unselect Hovered", CMNU_UNSELECT_HOVERED); } int ret = ShowContextMenu(); switch(ret) { case CMNU_UNSELECT_ALL: MenuEdit(MNU_UNSELECT_ALL); break; case CMNU_UNSELECT_HOVERED: if(!hover.IsEmpty()) { MakeUnselected(&hover, true); } break; case CMNU_SELECT_CHAIN: MenuEdit(MNU_SELECT_CHAIN); break; case CMNU_CUT_SEL: MenuClipboard(MNU_CUT); break; case CMNU_COPY_SEL: MenuClipboard(MNU_COPY); break; case CMNU_PASTE_SEL: MenuClipboard(MNU_PASTE); break; case CMNU_DELETE_SEL: MenuClipboard(MNU_DELETE); break; case CMNU_REFERENCE_DIM: Constraint::MenuConstrain(MNU_REFERENCE); break; case CMNU_OTHER_ANGLE: Constraint::MenuConstrain(MNU_OTHER_ANGLE); break; case CMNU_DEL_COINCIDENT: { SS.UndoRemember(); if(!gs.point[0].v) break; Entity *p = SK.GetEntity(gs.point[0]); if(!p->IsPoint()) break; SK.constraint.ClearTags(); Constraint *c; for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { if(c->type != Constraint::POINTS_COINCIDENT) continue; if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { c->tag = 1; } } SK.constraint.RemoveTagged(); ClearSelection(); break; } case CMNU_SNAP_TO_GRID: MenuEdit(MNU_SNAP_TO_GRID); break; case CMNU_GROUP_INFO: { hGroup hg; if(gs.entities == 1) { hg = SK.GetEntity(gs.entity[0])->group; } else if(gs.points == 1) { hg = SK.GetEntity(gs.point[0])->group; } else if(gs.constraints == 1) { hg = SK.GetConstraint(gs.constraint[0])->group; } else { break; } ClearSelection(); SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); SS.TW.shown.group = hg; SS.ScheduleShowTW(); break; } case CMNU_STYLE_INFO: { hStyle hs; if(gs.entities == 1) { hs = Style::ForEntity(gs.entity[0]); } else if(gs.points == 1) { hs = Style::ForEntity(gs.point[0]); } else if(gs.constraints == 1) { hs = SK.GetConstraint(gs.constraint[0])->disp.style; if(!hs.v) hs.v = Style::CONSTRAINT; } else { break; } ClearSelection(); SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); SS.TW.shown.style = hs; SS.ScheduleShowTW(); break; } case CMNU_NEW_CUSTOM_STYLE: { uint32_t v = Style::CreateCustomStyle(); Style::AssignSelectionToStyle(v); break; } case CMNU_NO_STYLE: Style::AssignSelectionToStyle(0); break; default: if(ret >= CMNU_FIRST_STYLE) { Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); } else { // otherwise it was cancelled, so do nothing contextMenuCancelTime = GetMilliseconds(); } break; } context.active = false; SS.ScheduleShowTW(); }
void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) { if(GraphicsEditControlIsVisible()) return; if(context.active) return; SS.extraLine.draw = false; if(!orig.mouseDown) { // If someone drags the mouse into our window with the left button // already depressed, then we don't have our starting point; so // don't try. leftDown = false; } if(rightDown) { middleDown = true; shiftDown = !shiftDown; } if(SS.showToolbar) { if(ToolbarMouseMoved((int)x, (int)y)) { hover.Clear(); return; } } if(!leftDown && (pending.operation == DRAGGING_POINTS || pending.operation == DRAGGING_MARQUEE)) { ClearPending(); InvalidateGraphics(); } Point2d mp = Point2d::From(x, y); currentMousePosition = mp; if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { // Avoid accidentally panning (or rotating if shift is down) if the // user wants a context menu. return; } orig.startedMoving = true; // If the middle button is down, then mouse movement is used to pan and // rotate our view. This wins over everything else. if(middleDown) { hover.Clear(); double dx = (x - orig.mouse.x) / scale; double dy = (y - orig.mouse.y) / scale; if(!(shiftDown || ctrlDown)) { double s = 0.3*(PI/180)*scale; // degrees per pixel projRight = orig.projRight.RotatedAbout(orig.projUp, -s*dx); projUp = orig.projUp.RotatedAbout(orig.projRight, s*dy); NormalizeProjectionVectors(); } else if(ctrlDown) { double theta = atan2(orig.mouse.y, orig.mouse.x); theta -= atan2(y, x); SS.extraLine.draw = true; SS.extraLine.ptA = UnProjectPoint(Point2d::From(0, 0)); SS.extraLine.ptB = UnProjectPoint(mp); Vector normal = orig.projRight.Cross(orig.projUp); projRight = orig.projRight.RotatedAbout(normal, theta); projUp = orig.projUp.RotatedAbout(normal, theta); NormalizeProjectionVectors(); } else { offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; } orig.projRight = projRight; orig.projUp = projUp; orig.offset = offset; orig.mouse.x = x; orig.mouse.y = y; if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { if(havePainted) { SS.ScheduleShowTW(); } } InvalidateGraphics(); havePainted = false; return; } if(pending.operation == 0) { double dm = orig.mouse.DistanceTo(mp); // If we're currently not doing anything, then see if we should // start dragging something. if(leftDown && dm > 3) { Entity *e = NULL; if(hover.entity.v) e = SK.GetEntity(hover.entity); if(e && e->type != Entity::WORKPLANE) { Entity *e = SK.GetEntity(hover.entity); if(e->type == Entity::CIRCLE && selection.n <= 1) { // Drag the radius. ClearSelection(); pending.circle = hover.entity; pending.operation = DRAGGING_RADIUS; } else if(e->IsNormal()) { ClearSelection(); pending.normal = hover.entity; pending.operation = DRAGGING_NORMAL; } else { if(!hoverWasSelectedOnMousedown) { // The user clicked an unselected entity, which // means they're dragging just the hovered thing, // not the full selection. So clear all the selection // except that entity. ClearSelection(); MakeSelected(e->h); } StartDraggingBySelection(); if(!hoverWasSelectedOnMousedown) { // And then clear the selection again, since they // probably didn't want that selected if they just // were dragging it. ClearSelection(); } hover.Clear(); pending.operation = DRAGGING_POINTS; } } else if(hover.constraint.v && SK.GetConstraint(hover.constraint)->HasLabel()) { ClearSelection(); pending.constraint = hover.constraint; pending.operation = DRAGGING_CONSTRAINT; } if(pending.operation != 0) { // We just started a drag, so remember for the undo before // the drag changes anything. SS.UndoRemember(); } else { if(!hover.constraint.v) { // That's just marquee selection, which should not cause // an undo remember. if(dm > 10) { if(hover.entity.v) { // Avoid accidentally selecting workplanes when // starting drags. MakeUnselected(hover.entity, false); hover.Clear(); } pending.operation = DRAGGING_MARQUEE; orig.marqueePoint = UnProjectPoint(orig.mouseOnButtonDown); } } } } else { // Otherwise, just hit test and give up; but don't hit test // if the mouse is down, because then the user could hover // a point, mouse down (thus selecting it), and drag, in an // effort to drag the point, but instead hover a different // entity before we move far enough to start the drag. if(!leftDown) HitTestMakeSelection(mp); } return; } // If the user has started an operation from the menu, but not // completed it, then just do the selection. if(pending.operation < FIRST_PENDING) { HitTestMakeSelection(mp); return; } // We're currently dragging something; so do that. But if we haven't // painted since the last time we solved, do nothing, because there's // no sense solving a frame and not displaying it. if(!havePainted) { if(pending.operation == DRAGGING_POINTS && ctrlDown) { SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); SS.extraLine.draw = true; } return; } switch(pending.operation) { 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: 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(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 // reference pos orig.mouse = mp; break; } 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); qt = Quaternion::From(gn, -theta); SS.extraLine.draw = true; SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); double s = 0.3*(PI/180); // degrees per pixel qt = Quaternion::From(projUp, -s*dx).Times( Quaternion::From(projRight, s*dy)); } orig.mouse = mp; // Now apply this rotation to the points being dragged. List<hEntity> *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) { if(ctrlDown) { Vector p = e->PointGetNum(); p = p.Minus(SS.extraLine.ptA); p = qt.Rotate(p); p = p.Plus(SS.extraLine.ptA); e->PointForceTo(p); SS.MarkGroupDirtyByEntity(e->h); } 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 { List<hEntity> *lhe = &(pending.points); for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { UpdateDraggedPoint(*he, x, y); SS.MarkGroupDirtyByEntity(*he); } orig.mouse = mp; } break; case DRAGGING_NEW_CUBIC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); if(pending.point.v == hr.entity(4).v) { // The very first segment; dragging final point drags both // tangent points. Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(), p3 = SK.GetEntity(hr.entity(4))->PointGetNum(), p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)), p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); SK.GetEntity(hr.entity(1+1))->PointForceTo(p1); SK.GetEntity(hr.entity(1+2))->PointForceTo(p2); } else { // A subsequent segment; dragging point drags only final // tangent point. int i = SK.GetEntity(hr.entity(0))->extraPoints; Vector pn = SK.GetEntity(hr.entity(4+i))->PointGetNum(), pnm2 = SK.GetEntity(hr.entity(2+i))->PointGetNum(), pnm1 = (pn.Plus(pnm2)).ScaledBy(0.5); SK.GetEntity(hr.entity(3+i))->PointForceTo(pnm1); } orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } case DRAGGING_NEW_ARC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); Vector ona = SK.GetEntity(hr.entity(2))->PointGetNum(); Vector onb = SK.GetEntity(hr.entity(3))->PointGetNum(); Vector center = (ona.Plus(onb)).ScaledBy(0.5); SK.GetEntity(hr.entity(1))->PointForceTo(center); orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } case DRAGGING_NEW_RADIUS: case DRAGGING_RADIUS: { Entity *circle = SK.GetEntity(pending.circle); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Point2d c2 = ProjectPoint(center); double r = c2.DistanceTo(mp)/scale; SK.GetEntity(circle->distance)->DistanceForceTo(r); SS.MarkGroupDirtyByEntity(pending.circle); break; } case DRAGGING_NORMAL: { Entity *normal = SK.GetEntity(pending.normal); Vector p = SK.GetEntity(normal->point[0])->PointGetNum(); Point2d p2 = ProjectPoint(p); Quaternion q = normal->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); if(ctrlDown) { double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); theta -= atan2(y-p2.y, x-p2.x); Vector normal = projRight.Cross(projUp); u = u.RotatedAbout(normal, -theta); v = v.RotatedAbout(normal, -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); } orig.mouse = mp; normal->NormalForceTo(Quaternion::From(u, v)); SS.MarkGroupDirtyByEntity(pending.normal); break; } case DRAGGING_MARQUEE: orig.mouse = mp; InvalidateGraphics(); break; default: oops(); } if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT && pending.operation != DRAGGING_MARQUEE) { SS.GenerateAll(); } havePainted = false; }
//----------------------------------------------------------------------------- // Unselect an item, if it is selected. We can either unselect just that item, // or also unselect any coincident points. The latter is useful if the user // somehow selects two coincident points (like with select all), because it // would otherwise be impossible to de-select the lower of the two. //----------------------------------------------------------------------------- void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) { Selection stog; ZERO(&stog); stog.entity = he; MakeUnselected(&stog, coincidentPointTrick); }