From 9723f4e44fb383a15aaf210af702929e167c2706 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Fri, 4 Dec 2009 00:08:41 -0800 Subject: [PATCH] Add support for a clipboard, with cut, copy, and paste. This works only in a workplane; but this means that plane sketches are conveniently transformed from one plane to another. Also tweak snap to grid to ignore unsnappable entities instead of triggering an error, and to remove arbitrary limit on the number of entities / comments that will be snapped. [git-p4: depot-paths = "//depot/solvespace/": change = 2084] --- Makefile | 1 + clipboard.cpp | 212 ++++++++++++++++++++++++++++++++++++++++++++++++ graphicswin.cpp | 77 +++++++----------- mouse.cpp | 2 +- request.cpp | 12 +-- sketch.h | 15 ++-- solvespace.h | 11 +++ ui.h | 4 + wishlist.txt | 4 +- 9 files changed, 276 insertions(+), 62 deletions(-) create mode 100644 clipboard.cpp diff --git a/Makefile b/Makefile index 9502b34..3412c30 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\confscreen.obj \ $(OBJDIR)\graphicswin.obj \ $(OBJDIR)\modify.obj \ + $(OBJDIR)\clipboard.obj \ $(OBJDIR)\util.obj \ $(OBJDIR)\style.obj \ $(OBJDIR)\entity.obj \ diff --git a/clipboard.cpp b/clipboard.cpp new file mode 100644 index 0000000..d1fc132 --- /dev/null +++ b/clipboard.cpp @@ -0,0 +1,212 @@ +#include "solvespace.h" + +void SolveSpace::Clipboard::Clear(void) { + c.Clear(); + r.Clear(); +} + +hEntity SolveSpace::Clipboard::NewEntityFor(hEntity he) { + ClipboardRequest *cr; + for(cr = r.First(); cr; cr = r.NextAfter(cr)) { + if(cr->oldEnt.v == he.v) { + return cr->newReq.entity(0); + } + for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { + if(cr->oldPointEnt[i].v == he.v) { + return cr->newReq.entity(1+i); + } + } + } + return Entity::NO_ENTITY; +} + +bool SolveSpace::Clipboard::ContainsEntity(hEntity he) { + hEntity hen = NewEntityFor(he); + if(hen.v) { + return true; + } else { + return false; + } +} + +void GraphicsWindow::DeleteSelection(void) { + SK.request.ClearTags(); + SK.constraint.ClearTags(); + List *ls = &(selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + hRequest r = { 0 }; + if(s->entity.v && s->entity.isFromRequest()) { + r = s->entity.request(); + } + if(r.v && !r.IsFromReferences()) { + SK.request.Tag(r, 1); + } + if(s->constraint.v) { + SK.constraint.Tag(s->constraint, 1); + } + } + + SK.constraint.RemoveTagged(); + // Note that this regenerates and clears the selection, to avoid + // lingering references to the just-deleted items. + DeleteTaggedRequests(); +} + +void GraphicsWindow::CopySelection(void) { + SS.clipboard.Clear(); + + Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); + Entity *wrkpln = SK.GetEntity(wrkpl->normal); + Vector u = wrkpln->NormalU(), + v = wrkpln->NormalV(), + n = wrkpln->NormalN(), + p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + + List *ls = &(selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + if(!s->entity.v) continue; + // Work only on entities that have requests that will generate them. + Entity *e = SK.GetEntity(s->entity); + bool hasDistance; + int req, pts; + if(!EntReqTable::GetEntityInfo(e->type, e->extraPoints, + &req, &pts, NULL, &hasDistance)) + { + continue; + } + if(req == Request::WORKPLANE) continue; + + ClipboardRequest cr; + ZERO(&cr); + cr.type = req; + cr.extraPoints = e->extraPoints; + cr.style = e->style; + cr.str.strcpy( e->str.str); + cr.font.strcpy( e->font.str); + cr.construction = e->construction; + for(int i = 0; i < pts; i++) { + Vector pt = SK.GetEntity(e->point[i])->PointGetNum(); + pt = pt.Minus(p); + pt = pt.DotInToCsys(u, v, n); + cr.point[i] = pt; + } + if(hasDistance) { + cr.distance = SK.GetEntity(e->distance)->DistanceGetNum(); + } + + cr.oldEnt = e->h; + for(int i = 0; i < pts; i++) { + cr.oldPointEnt[i] = e->point[i]; + } + + SS.clipboard.r.Add(&cr); + } + + Constraint *c; + for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { + if(c->type == Constraint::POINTS_COINCIDENT) { + if(!SS.clipboard.ContainsEntity(c->ptA)) continue; + if(!SS.clipboard.ContainsEntity(c->ptB)) continue; + } else { + continue; + } + SS.clipboard.c.Add(c); + } +} + +void GraphicsWindow::PasteClipboard(Vector trans, double theta, bool mirror) { + SS.UndoRemember(); + ClearSelection(); + + Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); + Entity *wrkpln = SK.GetEntity(wrkpl->normal); + Vector u = wrkpln->NormalU(), + v = wrkpln->NormalV(), + p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + + + ClipboardRequest *cr; + for(cr = SS.clipboard.r.First(); cr; cr = SS.clipboard.r.NextAfter(cr)) { + hRequest hr = AddRequest(cr->type, false); + Request *r = SK.GetRequest(hr); + r->extraPoints = cr->extraPoints; + r->style = cr->style; + r->str.strcpy( cr->str.str); + r->font.strcpy( cr->font.str); + r->construction = cr->construction; + // Need to regen to get the right number of points, if extraPoints + // changed. + SS.GenerateAll(-1, -1); + SS.MarkGroupDirty(r->group); + bool hasDistance; + int pts; + EntReqTable::GetRequestInfo(r->type, r->extraPoints, + NULL, &pts, NULL, &hasDistance); + for(int i = 0; i < pts; i++) { + Vector pt = cr->point[i]; + if(mirror) pt.x *= -1; + pt = Vector::From( cos(theta)*pt.x + sin(theta)*pt.y, + -sin(theta)*pt.x + cos(theta)*pt.y, + 0); + pt = pt.ScaleOutOfCsys(u, v, Vector::From(0, 0, 0)); + pt = pt.Plus(p); + pt = pt.Plus(trans); + SK.GetEntity(hr.entity(i+1))->PointForceTo(pt); + } + if(hasDistance) { + SK.GetEntity(hr.entity(64))->DistanceForceTo(cr->distance); + } + + cr->newReq = hr; + ToggleSelectionStateOf(hr.entity(0)); + } + + Constraint *c; + for(c = SS.clipboard.c.First(); c; c = SS.clipboard.c.NextAfter(c)) { + if(c->type == Constraint::POINTS_COINCIDENT) { + Constraint::ConstrainCoincident(SS.clipboard.NewEntityFor(c->ptA), + SS.clipboard.NewEntityFor(c->ptB)); + } + } + + SS.later.generateAll = true; +} + +void GraphicsWindow::MenuClipboard(int id) { + if(id != MNU_DELETE && !SS.GW.LockedInWorkplane()) { + Error("Cut, paste, and copy work only in a workplane.\r\n\r\n" + "Select one with Sketch -> In Workplane."); + return; + } + + switch(id) { + case MNU_PASTE: { + Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus( + SS.GW.projUp .ScaledBy(40/SS.GW.scale)); + SS.GW.PasteClipboard(trans, 0, false); + break; + } + + case MNU_PASTE_TRANSFORM: + break; + + case MNU_COPY: + SS.GW.CopySelection(); + SS.GW.ClearSelection(); + break; + + case MNU_CUT: + SS.UndoRemember(); + SS.GW.CopySelection(); + SS.GW.DeleteSelection(); + break; + + case MNU_DELETE: + SS.UndoRemember(); + SS.GW.DeleteSelection(); + break; + + default: oops(); + } +} + diff --git a/graphicswin.cpp b/graphicswin.cpp index 49726a0..0eeeec2 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -2,6 +2,7 @@ #define mView (&GraphicsWindow::MenuView) #define mEdit (&GraphicsWindow::MenuEdit) +#define mClip (&GraphicsWindow::MenuClipboard) #define mReq (&GraphicsWindow::MenuRequest) #define mCon (&Constraint::MenuConstrain) #define mFile (&SolveSpace::MenuFile) @@ -36,11 +37,11 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 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, "Paste &Transformed...\tCtrl+T", MNU_PASTE_TRANSFORM,'T'|C, mEdit }, -{ 1, "&Delete\tDel", MNU_DELETE, 127, mEdit }, +{ 1, "Cu&t\tCtrl+X", MNU_CUT, 'X'|C, mClip }, +{ 1, "&Copy\tCtrl+C", MNU_COPY, 'C'|C, mClip }, +{ 1, "&Paste\tCtrl+V", MNU_PASTE, 'V'|C, mClip }, +{ 1, "Paste &Transformed...\tCtrl+T", MNU_PASTE_TRANSFORM,'T'|C, mClip }, +{ 1, "&Delete\tDel", MNU_DELETE, 127, mClip }, { 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 }, @@ -358,9 +359,9 @@ void GraphicsWindow::MenuView(int id) { if(SS.cameraTangent < 1e-6) { Error("The perspective factor is set to zero, so the view will " "always be a parallel projection.\r\n\r\n" - "For a perspective projection, modify the camera tangent " - "in the configuration screen. A value around 0.3 is " - "typical."); + "For a perspective projection, modify the perspective " + "factor in the configuration screen. A value around 0.3 " + "is typical."); } SS.GW.EnsureValidActives(); InvalidateGraphics(); @@ -689,30 +690,6 @@ void GraphicsWindow::MenuEdit(int id) { break; } - case MNU_DELETE: { - SS.UndoRemember(); - - SK.request.ClearTags(); - SK.constraint.ClearTags(); - 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(); - } - if(r.v && !r.IsFromReferences()) { - SK.request.Tag(r, 1); - } - if(s->constraint.v) { - SK.constraint.Tag(s->constraint, 1); - } - } - - SK.constraint.RemoveTagged(); - SS.GW.DeleteTaggedRequests(); - break; - } - case MNU_ROTATE_90: { SS.GW.GroupSelection(); Entity *e = NULL; @@ -756,34 +733,36 @@ void GraphicsWindow::MenuEdit(int id) { break; } SS.GW.GroupSelection(); - if(SS.GW.gs.n != SS.GW.gs.points || - SS.GW.gs.constraints != SS.GW.gs.comments || - (SS.GW.gs.n == 0 && SS.GW.gs.constraints == 0)) - { - Error("Can't snap these items to grid; select only points or " + if(SS.GW.gs.points == 0 && SS.GW.gs.comments == 0) { + Error("Can't snap these items to grid; select points or " "text comments. To snap a line, select its endpoints."); break; } SS.UndoRemember(); - int i; - for(i = 0; i < SS.GW.gs.points; i++) { - hEntity hp = SS.GW.gs.point[i]; - Entity *ep = SK.GetEntity(hp); - Vector p = ep->PointGetNum(); - ep->PointForceTo(SS.GW.SnapToGrid(p)); - SS.GW.pending.points.Add(&hp); - SS.MarkGroupDirty(ep->group); + List *ls = &(SS.GW.selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + if(s->entity.v) { + hEntity hp = s->entity; + Entity *ep = SK.GetEntity(hp); + if(!ep->IsPoint()) continue; + + Vector p = ep->PointGetNum(); + ep->PointForceTo(SS.GW.SnapToGrid(p)); + SS.GW.pending.points.Add(&hp); + SS.MarkGroupDirty(ep->group); + } else if(s->constraint.v) { + Constraint *c = SK.GetConstraint(s->constraint); + if(c->type != Constraint::COMMENT) continue; + + c->disp.offset = SS.GW.SnapToGrid(c->disp.offset); + } } // 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); - } SS.GW.ClearSelection(); InvalidateGraphics(); break; diff --git a/mouse.cpp b/mouse.cpp index e5f71b3..4ebc938 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -592,7 +592,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { break; case CMNU_DELETE_SEL: - MenuEdit(MNU_DELETE); + MenuClipboard(MNU_DELETE); break; case CMNU_REFERENCE_DIM: diff --git a/request.cpp b/request.cpp index 09b3a09..f18a4f9 100644 --- a/request.cpp +++ b/request.cpp @@ -39,7 +39,7 @@ void EntReqTable::CopyEntityInfo(const TableEntry *te, int extraPoints, if(hasDistance) *hasDistance = te->hasDistance; } -void EntReqTable::GetRequestInfo(int req, int extraPoints, +bool EntReqTable::GetRequestInfo(int req, int extraPoints, int *ent, int *pts, bool *hasNormal, bool *hasDistance) { for(int i = 0; Table[i].reqType; i++) { @@ -47,13 +47,13 @@ void EntReqTable::GetRequestInfo(int req, int extraPoints, if(req == te->reqType) { CopyEntityInfo(te, extraPoints, ent, NULL, pts, hasNormal, hasDistance); - return; + return true; } } - oops(); + return false; } -void EntReqTable::GetEntityInfo(int ent, int extraPoints, +bool EntReqTable::GetEntityInfo(int ent, int extraPoints, int *req, int *pts, bool *hasNormal, bool *hasDistance) { for(int i = 0; Table[i].reqType; i++) { @@ -61,10 +61,10 @@ void EntReqTable::GetEntityInfo(int ent, int extraPoints, if(ent == te->entType) { CopyEntityInfo(te, extraPoints, NULL, req, pts, hasNormal, hasDistance); - return; + return true; } } - oops(); + return false; } int EntReqTable::GetRequestForEntity(int ent) { diff --git a/sketch.h b/sketch.h index f12b241..a48e4e0 100644 --- a/sketch.h +++ b/sketch.h @@ -476,9 +476,9 @@ public: static char *DescriptionForRequest(int req); static void CopyEntityInfo(const TableEntry *te, int extraPoints, int *ent, int *req, int *pts, bool *hasNormal, bool *hasDistance); - static void GetRequestInfo(int req, int extraPoints, + static bool GetRequestInfo(int req, int extraPoints, int *ent, int *pts, bool *hasNormal, bool *hasDistance); - static void GetEntityInfo(int ent, int extraPoints, + static bool GetEntityInfo(int ent, int extraPoints, int *req, int *pts, bool *hasNormal, bool *hasDistance); static int GetRequestForEntity(int ent); }; @@ -767,16 +767,21 @@ inline hConstraint hEquation::constraint(void) { hConstraint r; r.v = (v >> 16); return r; } // The format for entities stored on the clipboard. -class ClipboardEntity { +class ClipboardRequest { public: int type; - Vector point[MAX_POINTS_IN_ENTITY]; int extraPoints; - hStyle style; NameStr str; NameStr font; bool construction; + + Vector point[MAX_POINTS_IN_ENTITY]; + double distance; + + hEntity oldEnt; + hEntity oldPointEnt[MAX_POINTS_IN_ENTITY]; + hRequest newReq; }; #endif diff --git a/solvespace.h b/solvespace.h index 6046c67..5f79d8d 100644 --- a/solvespace.h +++ b/solvespace.h @@ -684,6 +684,17 @@ public: Vector origin; } bgImage; + class Clipboard { +public: + List r; + List c; + + void Clear(void); + bool ContainsEntity(hEntity old); + hEntity NewEntityFor(hEntity old); + }; + Clipboard clipboard; + void MarkGroupDirty(hGroup hg); void MarkGroupDirtyByEntity(hEntity he); diff --git a/ui.h b/ui.h index 8b6e862..baee4e9 100644 --- a/ui.h +++ b/ui.h @@ -315,6 +315,10 @@ public: static void MenuView(int id); static void MenuEdit(int id); static void MenuRequest(int id); + void DeleteSelection(void); + void CopySelection(void); + void PasteClipboard(Vector trans, double theta, bool mirror); + static void MenuClipboard(int id); // The width and height (in pixels) of the window. double width, height; diff --git a/wishlist.txt b/wishlist.txt index a815d77..91ce476 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,8 @@ -copy and paste +paste transformed de-select after left-clicking nothing, keep sel on drag? +bbox selection is select-only, not toggle? +show and modify view parameters (translate, rotate, scale) ----- associative entities from solid model, as a special group