1 //-----------------------------------------------------------------------------
2 // The clipboard that gets manipulated when the user selects Edit -> Cut,
3 // Copy, Paste, etc.; may contain entities only, not constraints.
4 //
5 // Copyright 2008-2013 Jonathan Westhues.
6 //-----------------------------------------------------------------------------
7 #include "solvespace.h"
8 
Clear(void)9 void SolveSpaceUI::Clipboard::Clear(void) {
10     c.Clear();
11     r.Clear();
12 }
13 
ContainsEntity(hEntity he)14 bool SolveSpaceUI::Clipboard::ContainsEntity(hEntity he) {
15     if(he.v == Entity::NO_ENTITY.v)
16         return true;
17 
18     ClipboardRequest *cr;
19     for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
20         if(cr->oldEnt.v == he.v)
21             return true;
22 
23         for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
24             if(cr->oldPointEnt[i].v == he.v)
25                 return true;
26         }
27     }
28     return false;
29 }
30 
NewEntityFor(hEntity he)31 hEntity SolveSpaceUI::Clipboard::NewEntityFor(hEntity he) {
32     if(he.v == Entity::NO_ENTITY.v)
33         return Entity::NO_ENTITY;
34 
35     ClipboardRequest *cr;
36     for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
37         if(cr->oldEnt.v == he.v)
38             return cr->newReq.entity(0);
39 
40         for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
41             if(cr->oldPointEnt[i].v == he.v)
42                 return cr->newReq.entity(1+i);
43         }
44     }
45     oops();
46 }
47 
DeleteSelection(void)48 void GraphicsWindow::DeleteSelection(void) {
49     SK.request.ClearTags();
50     SK.constraint.ClearTags();
51     List<Selection> *ls = &(selection);
52     for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
53         hRequest r = { 0 };
54         if(s->entity.v && s->entity.isFromRequest()) {
55             r = s->entity.request();
56         }
57         if(r.v && !r.IsFromReferences()) {
58             SK.request.Tag(r, 1);
59         }
60         if(s->constraint.v) {
61             SK.constraint.Tag(s->constraint, 1);
62         }
63     }
64 
65     SK.constraint.RemoveTagged();
66     // Note that this regenerates and clears the selection, to avoid
67     // lingering references to the just-deleted items.
68     DeleteTaggedRequests();
69 }
70 
CopySelection(void)71 void GraphicsWindow::CopySelection(void) {
72     SS.clipboard.Clear();
73 
74     Entity *wrkpl  = SK.GetEntity(ActiveWorkplane());
75     Entity *wrkpln = SK.GetEntity(wrkpl->normal);
76     Vector u = wrkpln->NormalU(),
77            v = wrkpln->NormalV(),
78            n = wrkpln->NormalN(),
79            p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
80 
81     List<Selection> *ls = &(selection);
82     for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
83         if(!s->entity.v) continue;
84         // Work only on entities that have requests that will generate them.
85         Entity *e = SK.GetEntity(s->entity);
86         bool hasDistance;
87         int req, pts;
88         if(!EntReqTable::GetEntityInfo(e->type, e->extraPoints,
89                 &req, &pts, NULL, &hasDistance))
90         {
91             continue;
92         }
93         if(req == Request::WORKPLANE) continue;
94 
95         ClipboardRequest cr = {};
96         cr.type         = req;
97         cr.extraPoints  = e->extraPoints;
98         cr.style        = e->style;
99         cr.str          = e->str;
100         cr.font         = e->font;
101         cr.construction = e->construction;
102         {for(int i = 0; i < pts; i++) {
103             Vector pt = SK.GetEntity(e->point[i])->PointGetNum();
104             pt = pt.Minus(p);
105             pt = pt.DotInToCsys(u, v, n);
106             cr.point[i] = pt;
107         }}
108         if(hasDistance) {
109             cr.distance = SK.GetEntity(e->distance)->DistanceGetNum();
110         }
111 
112         cr.oldEnt = e->h;
113         for(int i = 0; i < pts; i++) {
114             cr.oldPointEnt[i] = e->point[i];
115         }
116 
117         SS.clipboard.r.Add(&cr);
118     }
119 
120     for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
121         if(!s->constraint.v) continue;
122 
123         Constraint *c = SK.GetConstraint(s->constraint);
124         if(c->type == Constraint::COMMENT) {
125             SS.clipboard.c.Add(c);
126         }
127     }
128 
129     Constraint *c;
130     for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
131         if(!SS.clipboard.ContainsEntity(c->ptA) ||
132            !SS.clipboard.ContainsEntity(c->ptB) ||
133            !SS.clipboard.ContainsEntity(c->entityA) ||
134            !SS.clipboard.ContainsEntity(c->entityB) ||
135            !SS.clipboard.ContainsEntity(c->entityC) ||
136            !SS.clipboard.ContainsEntity(c->entityD) ||
137            c->type == Constraint::COMMENT) {
138             continue;
139         }
140         SS.clipboard.c.Add(c);
141     }
142 }
143 
PasteClipboard(Vector trans,double theta,double scale)144 void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
145     Entity *wrkpl  = SK.GetEntity(ActiveWorkplane());
146     Entity *wrkpln = SK.GetEntity(wrkpl->normal);
147     Vector u = wrkpln->NormalU(),
148            v = wrkpln->NormalV(),
149            n = wrkpln->NormalN(),
150            p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
151 
152     ClipboardRequest *cr;
153     for(cr = SS.clipboard.r.First(); cr; cr = SS.clipboard.r.NextAfter(cr)) {
154         hRequest hr = AddRequest(cr->type, false);
155         Request *r = SK.GetRequest(hr);
156         r->extraPoints  = cr->extraPoints;
157         r->style        = cr->style;
158         r->str          = cr->str;
159         r->font         = cr->font;
160         r->construction = cr->construction;
161         // Need to regen to get the right number of points, if extraPoints
162         // changed.
163         SS.GenerateAll(SolveSpaceUI::GENERATE_REGEN);
164         SS.MarkGroupDirty(r->group);
165         bool hasDistance;
166         int i, pts;
167         EntReqTable::GetRequestInfo(r->type, r->extraPoints,
168             NULL, &pts, NULL, &hasDistance);
169         for(i = 0; i < pts; i++) {
170             Vector pt = cr->point[i];
171             // We need the reflection to occur within the workplane; it may
172             // otherwise correspond to just a rotation as projected.
173             if(scale < 0) {
174                 pt.x *= -1;
175             }
176             // Likewise the scale, which could otherwise take us out of the
177             // workplane.
178             pt = pt.ScaledBy(fabs(scale));
179             pt = pt.ScaleOutOfCsys(u, v, Vector::From(0, 0, 0));
180             pt = pt.Plus(p);
181             pt = pt.RotatedAbout(n, theta);
182             pt = pt.Plus(trans);
183             SK.GetEntity(hr.entity(i+1))->PointForceTo(pt);
184         }
185         if(hasDistance) {
186             SK.GetEntity(hr.entity(64))->DistanceForceTo(
187                                             cr->distance*fabs(scale));
188         }
189 
190         cr->newReq = hr;
191         MakeSelected(hr.entity(0));
192         for(i = 0; i < pts; i++) {
193             MakeSelected(hr.entity(i+1));
194         }
195     }
196 
197     Constraint *cc;
198     for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) {
199         Constraint c = {};
200         c.group = SS.GW.activeGroup;
201         c.workplane = SS.GW.ActiveWorkplane();
202         c.type = cc->type;
203         c.valA = cc->valA;
204         c.ptA = SS.clipboard.NewEntityFor(cc->ptA);
205         c.ptB = SS.clipboard.NewEntityFor(cc->ptB);
206         c.entityA = SS.clipboard.NewEntityFor(cc->entityA);
207         c.entityB = SS.clipboard.NewEntityFor(cc->entityB);
208         c.entityC = SS.clipboard.NewEntityFor(cc->entityC);
209         c.entityD = SS.clipboard.NewEntityFor(cc->entityD);
210         c.other = cc->other;
211         c.other2 = cc->other2;
212         c.reference = cc->reference;
213         c.disp = cc->disp;
214         c.comment = cc->comment;
215         switch(c.type) {
216             case Constraint::COMMENT:
217                 c.disp.offset = c.disp.offset.Plus(trans);
218                 break;
219 
220             case Constraint::PT_PT_DISTANCE:
221             case Constraint::PT_LINE_DISTANCE:
222             case Constraint::PROJ_PT_DISTANCE:
223             case Constraint::DIAMETER:
224                 c.valA *= fabs(scale);
225                 break;
226 
227             default:
228                 break;
229         }
230 
231         hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
232         if(c.type == Constraint::COMMENT) {
233             MakeSelected(hc);
234         }
235     }
236 
237     SS.ScheduleGenerateAll();
238 }
239 
MenuClipboard(int id)240 void GraphicsWindow::MenuClipboard(int id) {
241     if(id != MNU_DELETE && !SS.GW.LockedInWorkplane()) {
242         Error("Cut, paste, and copy work only in a workplane.\n\n"
243               "Select one with Sketch -> In Workplane.");
244         return;
245     }
246 
247     switch(id) {
248         case MNU_PASTE: {
249             SS.UndoRemember();
250             Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus(
251                            SS.GW.projUp   .ScaledBy(40/SS.GW.scale));
252             SS.GW.ClearSelection();
253             SS.GW.PasteClipboard(trans, 0, 1);
254             break;
255         }
256 
257         case MNU_PASTE_TRANSFORM: {
258             if(SS.clipboard.r.n == 0) {
259                 Error("Clipboard is empty; nothing to paste.");
260                 break;
261             }
262 
263             Entity *wrkpl  = SK.GetEntity(SS.GW.ActiveWorkplane());
264             Vector p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
265             SS.TW.shown.paste.times  = 1;
266             SS.TW.shown.paste.trans  = Vector::From(0, 0, 0);
267             SS.TW.shown.paste.theta  = 0;
268             SS.TW.shown.paste.origin = p;
269             SS.TW.shown.paste.scale  = 1;
270             SS.TW.GoToScreen(TextWindow::SCREEN_PASTE_TRANSFORMED);
271             SS.GW.ForceTextWindowShown();
272             SS.ScheduleShowTW();
273             break;
274         }
275 
276         case MNU_COPY:
277             SS.GW.CopySelection();
278             SS.GW.ClearSelection();
279             break;
280 
281         case MNU_CUT:
282             SS.UndoRemember();
283             SS.GW.CopySelection();
284             SS.GW.DeleteSelection();
285             break;
286 
287         case MNU_DELETE:
288             SS.UndoRemember();
289             SS.GW.DeleteSelection();
290             break;
291 
292         default: oops();
293     }
294 }
295 
EditControlDoneForPaste(const char * s)296 bool TextWindow::EditControlDoneForPaste(const char *s) {
297     Expr *e;
298     switch(edit.meaning) {
299         case EDIT_PASTE_TIMES_REPEATED: {
300             e = Expr::From(s, true);
301             if(!e) break;
302             int v = (int)e->Eval();
303             if(v > 0) {
304                 shown.paste.times = v;
305             } else {
306                 Error("Number of copies to paste must be at least one.");
307             }
308             break;
309         }
310         case EDIT_PASTE_ANGLE:
311             e = Expr::From(s, true);
312             if(!e) break;
313             shown.paste.theta = WRAP_SYMMETRIC((e->Eval())*PI/180, 2*PI);
314             break;
315 
316         case EDIT_PASTE_SCALE: {
317             e = Expr::From(s, true);
318             double v = e->Eval();
319             if(fabs(v) > 1e-6) {
320                 shown.paste.scale = v;
321             } else {
322                 Error("Scale cannot be zero.");
323             }
324             break;
325         }
326 
327         default:
328             return false;
329     }
330     return true;
331 }
332 
ScreenChangePasteTransformed(int link,uint32_t v)333 void TextWindow::ScreenChangePasteTransformed(int link, uint32_t v) {
334     switch(link) {
335         case 't':
336             SS.TW.ShowEditControl(13, ssprintf("%d", SS.TW.shown.paste.times));
337             SS.TW.edit.meaning = EDIT_PASTE_TIMES_REPEATED;
338             break;
339 
340         case 'r':
341             SS.TW.ShowEditControl(13, ssprintf("%.3f", SS.TW.shown.paste.theta*180/PI));
342             SS.TW.edit.meaning = EDIT_PASTE_ANGLE;
343             break;
344 
345         case 's':
346             SS.TW.ShowEditControl(13, ssprintf("%.3f", SS.TW.shown.paste.scale));
347             SS.TW.edit.meaning = EDIT_PASTE_SCALE;
348             break;
349     }
350 }
351 
ScreenPasteTransformed(int link,uint32_t v)352 void TextWindow::ScreenPasteTransformed(int link, uint32_t v) {
353     SS.GW.GroupSelection();
354     switch(link) {
355         case 'o':
356             if(SS.GW.gs.points == 1 && SS.GW.gs.n == 1) {
357                 Entity *e = SK.GetEntity(SS.GW.gs.point[0]);
358                 SS.TW.shown.paste.origin = e->PointGetNum();
359             } else {
360                 Error("Select one point to define origin of rotation.");
361             }
362             SS.GW.ClearSelection();
363             break;
364 
365         case 't':
366             if(SS.GW.gs.points == 2 && SS.GW.gs.n == 2) {
367                 Entity *pa = SK.GetEntity(SS.GW.gs.point[0]),
368                        *pb = SK.GetEntity(SS.GW.gs.point[1]);
369                 SS.TW.shown.paste.trans =
370                     (pb->PointGetNum()).Minus(pa->PointGetNum());
371             } else {
372                 Error("Select two points to define translation vector.");
373             }
374             SS.GW.ClearSelection();
375             break;
376 
377         case 'g': {
378             if(fabs(SS.TW.shown.paste.theta) < LENGTH_EPS &&
379                SS.TW.shown.paste.trans.Magnitude() < LENGTH_EPS &&
380                SS.TW.shown.paste.times != 1)
381             {
382                 Message("Transformation is identity. So all copies will be "
383                         "exactly on top of each other.");
384             }
385             if(SS.TW.shown.paste.times*SS.clipboard.r.n > 100) {
386                 Error("Too many items to paste; split this into smaller "
387                       "pastes.");
388                 break;
389             }
390             if(!SS.GW.LockedInWorkplane()) {
391                 Error("No workplane active.");
392                 break;
393             }
394             Entity *wrkpl  = SK.GetEntity(SS.GW.ActiveWorkplane());
395             Entity *wrkpln = SK.GetEntity(wrkpl->normal);
396             Vector wn = wrkpln->NormalN();
397             SS.UndoRemember();
398             SS.GW.ClearSelection();
399             for(int i = 0; i < SS.TW.shown.paste.times; i++) {
400                 Vector trans  = SS.TW.shown.paste.trans.ScaledBy(i+1),
401                        origin = SS.TW.shown.paste.origin;
402                 double theta = SS.TW.shown.paste.theta*(i+1);
403                 // desired transformation is Q*(p - o) + o + t =
404                 // Q*p - Q*o + o + t = Q*p + (t + o - Q*o)
405                 Vector t = trans.Plus(
406                            origin).Minus(
407                            origin.RotatedAbout(wn, theta));
408 
409                 SS.GW.PasteClipboard(t, theta, SS.TW.shown.paste.scale);
410             }
411             SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS);
412             SS.ScheduleShowTW();
413             break;
414         }
415     }
416 }
417 
ShowPasteTransformed(void)418 void TextWindow::ShowPasteTransformed(void) {
419     Printf(true, "%FtPASTE TRANSFORMED%E");
420     Printf(true,  "%Ba   %Ftrepeat%E    %d time%s %Fl%Lt%f[change]%E",
421         shown.paste.times, (shown.paste.times == 1) ? "" : "s",
422         &ScreenChangePasteTransformed);
423     Printf(false, "%Bd   %Ftrotate%E    %@ degrees %Fl%Lr%f[change]%E",
424         shown.paste.theta*180/PI,
425         &ScreenChangePasteTransformed);
426     Printf(false, "%Ba   %Ftabout pt%E  (%s, %s, %s) %Fl%Lo%f[use selected]%E",
427             SS.MmToString(shown.paste.origin.x).c_str(),
428             SS.MmToString(shown.paste.origin.y).c_str(),
429             SS.MmToString(shown.paste.origin.z).c_str(),
430         &ScreenPasteTransformed);
431     Printf(false, "%Bd   %Fttranslate%E (%s, %s, %s) %Fl%Lt%f[use selected]%E",
432             SS.MmToString(shown.paste.trans.x).c_str(),
433             SS.MmToString(shown.paste.trans.y).c_str(),
434             SS.MmToString(shown.paste.trans.z).c_str(),
435         &ScreenPasteTransformed);
436     Printf(false, "%Ba   %Ftscale%E     %@ %Fl%Ls%f[change]%E",
437         shown.paste.scale,
438         &ScreenChangePasteTransformed);
439 
440     Printf(true, " %Fl%Lg%fpaste transformed now%E", &ScreenPasteTransformed);
441 
442     Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome);
443 }
444 
445