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]
solver
Jonathan Westhues 2009-12-04 00:08:41 -08:00
parent c74ab47833
commit 9723f4e44f
9 changed files with 276 additions and 62 deletions

View File

@ -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 \

212
clipboard.cpp Normal file
View File

@ -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<Selection> *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<Selection> *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();
}
}

View File

@ -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<Selection> *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<Selection> *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;

View File

@ -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:

View File

@ -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) {

View File

@ -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

View File

@ -684,6 +684,17 @@ public:
Vector origin;
} bgImage;
class Clipboard {
public:
List<ClipboardRequest> r;
List<Constraint> c;
void Clear(void);
bool ContainsEntity(hEntity old);
hEntity NewEntityFor(hEntity old);
};
Clipboard clipboard;
void MarkGroupDirty(hGroup hg);
void MarkGroupDirtyByEntity(hEntity he);

4
ui.h
View File

@ -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;

View File

@ -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