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