src/actionsEdit.cc

branch
projects
changeset 935
8d98ee0dc917
parent 930
ab77deb851fa
parent 934
be8128aff739
child 936
aee883858c90
equal deleted inserted replaced
930:ab77deb851fa 935:8d98ee0dc917
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 - 2015 Teemu Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <limits>
20 #include <QSpinBox>
21 #include <QCheckBox>
22 #include <QBoxLayout>
23 #include <QClipboard>
24 #include <QInputDialog>
25 #include "mainWindow.h"
26 #include "main.h"
27 #include "ldDocument.h"
28 #include "colorSelector.h"
29 #include "miscallenous.h"
30 #include "radioGroup.h"
31 #include "glRenderer.h"
32 #include "dialogs.h"
33 #include "colors.h"
34 #include "ldObjectMath.h"
35 #include "ui_replcoords.h"
36 #include "ui_editraw.h"
37 #include "ui_flip.h"
38 #include "ui_addhistoryline.h"
39
40 EXTERN_CFGENTRY (String, DefaultUser)
41
42 CFGENTRY (Int, RoundPosition, 3)
43 CFGENTRY (Int, RoundMatrix, 4)
44 CFGENTRY (Int, SplitLinesSegments, 5)
45
46 // =============================================================================
47 //
48 static int CopyToClipboard()
49 {
50 LDObjectList objs = Selection();
51 int num = 0;
52
53 // Clear the clipboard first.
54 qApp->clipboard()->clear();
55
56 // Now, copy the contents into the clipboard.
57 QString data;
58
59 for (LDObjectPtr obj : objs)
60 {
61 if (not data.isEmpty())
62 data += "\n";
63
64 data += obj->asText();
65 ++num;
66 }
67
68 qApp->clipboard()->setText (data);
69 return num;
70 }
71
72 // =============================================================================
73 //
74 void MainWindow::actionCut()
75 {
76 int num = CopyToClipboard();
77 deleteSelection();
78 print (tr ("%1 objects cut"), num);
79 }
80
81 // =============================================================================
82 //
83 void MainWindow::actionCopy()
84 {
85 int num = CopyToClipboard();
86 print (tr ("%1 objects copied"), num);
87 }
88
89 // =============================================================================
90 //
91 void MainWindow::actionPaste()
92 {
93 const QString clipboardText = qApp->clipboard()->text();
94 int idx = getInsertionPoint();
95 CurrentDocument()->clearSelection();
96 int num = 0;
97
98 for (QString line : clipboardText.split ("\n"))
99 {
100 LDObjectPtr pasted = ParseLine (line);
101 CurrentDocument()->insertObj (idx++, pasted);
102 pasted->select();
103 ++num;
104 }
105
106 print (tr ("%1 objects pasted"), num);
107 refresh();
108 scrollToSelection();
109 }
110
111 // =============================================================================
112 //
113 void MainWindow::actionDelete()
114 {
115 int num = deleteSelection();
116 print (tr ("%1 objects deleted"), num);
117 }
118
119 // =============================================================================
120 //
121 static void DoInline (bool deep)
122 {
123 LDObjectList sel = Selection();
124
125 LDIterate<LDSubfile> (Selection(), [&](LDSubfilePtr const& ref)
126 {
127 // Get the index of the subfile so we know where to insert the
128 // inlined contents.
129 long idx = ref->lineNumber();
130
131 assert (idx != -1);
132 LDObjectList objs = ref->inlineContents (deep, false);
133
134 // Merge in the inlined objects
135 for (LDObjectPtr inlineobj : objs)
136 {
137 QString line = inlineobj->asText();
138 inlineobj->destroy();
139 LDObjectPtr newobj = ParseLine (line);
140 CurrentDocument()->insertObj (idx++, newobj);
141 newobj->select();
142 }
143
144 // Delete the subfile now as it's been inlined.
145 ref->destroy();
146 });
147
148 g_win->refresh();
149 }
150
151 void MainWindow::actionInline()
152 {
153 DoInline (false);
154 }
155
156 void MainWindow::actionInlineDeep()
157 {
158 DoInline (true);
159 }
160
161 // =============================================================================
162 //
163 void MainWindow::actionSplitQuads()
164 {
165 int num = 0;
166
167 LDIterate<LDQuad> (Selection(), [&](LDQuadPtr const& quad)
168 {
169 // Find the index of this quad
170 long index = quad->lineNumber();
171
172 if (index == -1)
173 return;
174
175 QList<LDTrianglePtr> triangles = quad->splitToTriangles();
176
177 // Replace the quad with the first triangle and add the second triangle
178 // after the first one.
179 CurrentDocument()->setObject (index, triangles[0]);
180 CurrentDocument()->insertObj (index + 1, triangles[1]);
181 num++;
182 });
183
184 print ("%1 quadrilaterals split", num);
185 refresh();
186 }
187
188 // =============================================================================
189 //
190 void MainWindow::actionEditRaw()
191 {
192 if (Selection().size() != 1)
193 return;
194
195 LDObjectPtr obj = Selection()[0];
196 QDialog* dlg = new QDialog;
197 Ui::EditRawUI ui;
198
199 ui.setupUi (dlg);
200 ui.code->setText (obj->asText());
201
202 if (obj->type() == OBJ_Error)
203 ui.errorDescription->setText (obj.staticCast<LDError>()->reason());
204 else
205 {
206 ui.errorDescription->hide();
207 ui.errorIcon->hide();
208 }
209
210 if (dlg->exec() == QDialog::Rejected)
211 return;
212
213 // Reinterpret it from the text of the input field
214 LDObjectPtr newobj = ParseLine (ui.code->text());
215 obj->replace (newobj);
216 refresh();
217 }
218
219 // =============================================================================
220 //
221 void MainWindow::actionSetColor()
222 {
223 if (Selection().isEmpty())
224 return;
225
226 LDObjectList objs = Selection();
227
228 // If all selected objects have the same color, said color is our default
229 // value to the color selection dialog.
230 LDColor color;
231 LDColor defaultcol = getSelectedColor();
232
233 // Show the dialog to the user now and ask for a color.
234 if (ColorSelector::selectColor (color, defaultcol, g_win))
235 {
236 for (LDObjectPtr obj : objs)
237 {
238 if (obj->isColored())
239 obj->setColor (color);
240 }
241
242 refresh();
243 }
244 }
245
246 // =============================================================================
247 //
248 void MainWindow::actionBorders()
249 {
250 LDObjectList objs = Selection();
251 int num = 0;
252
253 for (LDObjectPtr obj : objs)
254 {
255 const LDObjectType type = obj->type();
256
257 if (type != OBJ_Quad and type != OBJ_Triangle)
258 continue;
259
260 LDLinePtr lines[4];
261
262 if (type == OBJ_Quad)
263 {
264 LDQuadPtr quad = obj.staticCast<LDQuad>();
265 lines[0] = LDSpawn<LDLine> (quad->vertex (0), quad->vertex (1));
266 lines[1] = LDSpawn<LDLine> (quad->vertex (1), quad->vertex (2));
267 lines[2] = LDSpawn<LDLine> (quad->vertex (2), quad->vertex (3));
268 lines[3] = LDSpawn<LDLine> (quad->vertex (3), quad->vertex (0));
269 }
270 else
271 {
272 LDTrianglePtr tri = obj.staticCast<LDTriangle>();
273 lines[0] = LDSpawn<LDLine> (tri->vertex (0), tri->vertex (1));
274 lines[1] = LDSpawn<LDLine> (tri->vertex (1), tri->vertex (2));
275 lines[2] = LDSpawn<LDLine> (tri->vertex (2), tri->vertex (0));
276 }
277
278 for (int i = 0; i < countof (lines); ++i)
279 {
280 if (lines[i] == null)
281 continue;
282
283 long idx = obj->lineNumber() + i + 1;
284 CurrentDocument()->insertObj (idx, lines[i]);
285 }
286
287 num += countof (lines);
288 }
289
290 print (tr ("Added %1 border lines"), num);
291 refresh();
292 }
293
294 // =============================================================================
295 //
296 static void MoveSelection (const bool up)
297 {
298 LDObjectList objs = Selection();
299 LDObject::moveObjects (objs, up);
300 g_win->buildObjList();
301 }
302
303 // =============================================================================
304 //
305 void MainWindow::actionMoveUp()
306 {
307 MoveSelection (true);
308 }
309
310 void MainWindow::actionMoveDown()
311 {
312 MoveSelection (false);
313 }
314
315 // =============================================================================
316 //
317 void MainWindow::actionUndo()
318 {
319 CurrentDocument()->undo();
320 }
321
322 void MainWindow::actionRedo()
323 {
324 CurrentDocument()->redo();
325 }
326
327 // =============================================================================
328 //
329 static void MoveObjects (Vertex vect)
330 {
331 // Apply the grid values
332 vect *= *CurrentGrid().coordinateSnap;
333
334 for (LDObjectPtr obj : Selection())
335 obj->move (vect);
336
337 g_win->refresh();
338 }
339
340 // =============================================================================
341 //
342 void MainWindow::actionMoveXNeg()
343 {
344 MoveObjects ({-1, 0, 0});
345 }
346
347 void MainWindow::actionMoveYNeg()
348 {
349 MoveObjects ({0, -1, 0});
350 }
351
352 void MainWindow::actionMoveZNeg()
353 {
354 MoveObjects ({0, 0, -1});
355 }
356
357 void MainWindow::actionMoveXPos()
358 {
359 MoveObjects ({1, 0, 0});
360 }
361
362 void MainWindow::actionMoveYPos()
363 {
364 MoveObjects ({0, 1, 0});
365 }
366
367 void MainWindow::actionMoveZPos()
368 {
369 MoveObjects ({0, 0, 1});
370 }
371
372 // =============================================================================
373 //
374 void MainWindow::actionInvert()
375 {
376 for (LDObjectPtr obj : Selection())
377 obj->invert();
378
379 refresh();
380 }
381
382 // =============================================================================
383 //
384 static double GetRotateActionAngle()
385 {
386 return (Pi * *CurrentGrid().angleSnap) / 180;
387 }
388
389 void MainWindow::actionRotateXPos()
390 {
391 RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection());
392 }
393 void MainWindow::actionRotateYPos()
394 {
395 RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection());
396 }
397 void MainWindow::actionRotateZPos()
398 {
399 RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection());
400 }
401 void MainWindow::actionRotateXNeg()
402 {
403 RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection());
404 }
405 void MainWindow::actionRotateYNeg()
406 {
407 RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection());
408 }
409 void MainWindow::actionRotateZNeg()
410 {
411 RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection());
412 }
413
414 void MainWindow::actionRotationPoint()
415 {
416 ConfigureRotationPoint();
417 }
418
419 // =============================================================================
420 //
421 void MainWindow::actionRoundCoordinates()
422 {
423 setlocale (LC_ALL, "C");
424 int num = 0;
425
426 for (LDObjectPtr obj : Selection())
427 {
428 LDMatrixObjectPtr mo = obj.dynamicCast<LDMatrixObject>();
429
430 if (mo != null)
431 {
432 Vertex v = mo->position();
433 Matrix t = mo->transform();
434
435 // Note: matrix values are to be rounded to 4 decimals.
436 v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
437 ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); });
438
439 mo->setPosition (v);
440 mo->setTransform (t);
441 num += 12;
442 }
443 else
444 {
445 for (int i = 0; i < obj->numVertices(); ++i)
446 {
447 Vertex v = obj->vertex (i);
448 v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
449 obj->setVertex (i, v);
450 num += 3;
451 }
452 }
453 }
454
455 print (tr ("Rounded %1 values"), num);
456 refreshObjectList();
457 refresh();
458 }
459
460 // =============================================================================
461 //
462 void MainWindow::actionUncolor()
463 {
464 int num = 0;
465
466 for (LDObjectPtr obj : Selection())
467 {
468 if (not obj->isColored())
469 continue;
470
471 obj->setColor (obj->defaultColor());
472 num++;
473 }
474
475 print (tr ("%1 objects uncolored"), num);
476 refresh();
477 }
478
479 // =============================================================================
480 //
481 void MainWindow::actionReplaceCoords()
482 {
483 QDialog* dlg = new QDialog (g_win);
484 Ui::ReplaceCoordsUI ui;
485 ui.setupUi (dlg);
486
487 if (not dlg->exec())
488 return;
489
490 const double search = ui.search->value(),
491 replacement = ui.replacement->value();
492 const bool any = ui.any->isChecked(),
493 rel = ui.relative->isChecked();
494
495 QList<Axis> sel;
496 int num = 0;
497
498 if (ui.x->isChecked()) sel << X;
499 if (ui.y->isChecked()) sel << Y;
500 if (ui.z->isChecked()) sel << Z;
501
502 for (LDObjectPtr obj : Selection())
503 {
504 for (int i = 0; i < obj->numVertices(); ++i)
505 {
506 Vertex v = obj->vertex (i);
507
508 v.apply ([&](Axis ax, double& coord)
509 {
510 if (not sel.contains (ax) or
511 (not any and coord != search))
512 {
513 return;
514 }
515
516 if (not rel)
517 coord = 0;
518
519 coord += replacement;
520 num++;
521 });
522
523 obj->setVertex (i, v);
524 }
525 }
526
527 print (tr ("Altered %1 values"), num);
528 refresh();
529 }
530
531 // =============================================================================
532 //
533 void MainWindow::actionFlip()
534 {
535 QDialog* dlg = new QDialog;
536 Ui::FlipUI ui;
537 ui.setupUi (dlg);
538
539 if (not dlg->exec())
540 return;
541
542 QList<Axis> sel;
543
544 if (ui.x->isChecked()) sel << X;
545 if (ui.y->isChecked()) sel << Y;
546 if (ui.z->isChecked()) sel << Z;
547
548 for (LDObjectPtr obj : Selection())
549 {
550 for (int i = 0; i < obj->numVertices(); ++i)
551 {
552 Vertex v = obj->vertex (i);
553
554 v.apply ([&](Axis ax, double& a)
555 {
556 if (sel.contains (ax))
557 a = -a;
558 });
559
560 obj->setVertex (i, v);
561 }
562 }
563
564 refresh();
565 }
566
567 // =============================================================================
568 //
569 void MainWindow::actionDemote()
570 {
571 int num = 0;
572
573 LDIterate<LDCondLine> (Selection(), [&](LDCondLinePtr const& cnd)
574 {
575 cnd->toEdgeLine();
576 ++num;
577 });
578
579 print (tr ("Demoted %1 conditional lines"), num);
580 refresh();
581 }
582
583 // =============================================================================
584 //
585 static bool IsColorUsed (LDColor color)
586 {
587 for (LDObjectPtr obj : CurrentDocument()->objects())
588 {
589 if (obj->isColored() and obj->color() == color)
590 return true;
591 }
592
593 return false;
594 }
595
596 // =============================================================================
597 //
598 void MainWindow::actionAutocolor()
599 {
600 int colnum = 0;
601 LDColor color;
602
603 for (colnum = 0;
604 colnum < CountLDConfigColors() and
605 ((color = LDColor::fromIndex (colnum)) == null or
606 IsColorUsed (color));
607 colnum++) {}
608
609 if (colnum >= CountLDConfigColors())
610 {
611 print (tr ("Cannot auto-color: all colors are in use!"));
612 return;
613 }
614
615 for (LDObjectPtr obj : Selection())
616 {
617 if (not obj->isColored())
618 continue;
619
620 obj->setColor (color);
621 }
622
623 print (tr ("Auto-colored: new color is [%1] %2"), colnum, color.name());
624 refresh();
625 }
626
627 // =============================================================================
628 //
629 void MainWindow::actionAddHistoryLine()
630 {
631 LDObjectPtr obj;
632 bool ishistory = false,
633 prevIsHistory = false;
634
635 QDialog* dlg = new QDialog;
636 Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
637 ui->setupUi (dlg);
638 ui->m_username->setText (cfg::DefaultUser);
639 ui->m_date->setDate (QDate::currentDate());
640 ui->m_comment->setFocus();
641
642 if (not dlg->exec())
643 return;
644
645 // Create the comment object based on input
646 QString commentText = format ("!HISTORY %1 [%2] %3",
647 ui->m_date->date().toString ("yyyy-MM-dd"),
648 ui->m_username->text(),
649 ui->m_comment->text());
650
651 LDCommentPtr comm (LDSpawn<LDComment> (commentText));
652
653 // Find a spot to place the new comment
654 for (obj = CurrentDocument()->getObject (0);
655 obj != null and obj->next() != null and not obj->next()->isScemantic();
656 obj = obj->next())
657 {
658 LDCommentPtr comm = obj.dynamicCast<LDComment>();
659
660 if (comm != null and comm->text().startsWith ("!HISTORY "))
661 ishistory = true;
662
663 if (prevIsHistory and not ishistory)
664 {
665 // Last line was history, this isn't, thus insert the new history
666 // line here.
667 break;
668 }
669
670 prevIsHistory = ishistory;
671 }
672
673 int idx = obj ? obj->lineNumber() : 0;
674 CurrentDocument()->insertObj (idx++, comm);
675
676 // If we're adding a history line right before a scemantic object, pad it
677 // an empty line
678 if (obj and obj->next() and obj->next()->isScemantic())
679 CurrentDocument()->insertObj (idx, LDEmptyPtr (LDSpawn<LDEmpty>()));
680
681 buildObjList();
682 delete ui;
683 }
684
685 void MainWindow::actionSplitLines()
686 {
687 bool ok;
688 int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0,
689 std::numeric_limits<int>::max(), 1, &ok);
690
691 if (not ok)
692 return;
693
694 cfg::SplitLinesSegments = segments;
695
696 for (LDObjectPtr obj : Selection())
697 {
698 if (not Eq (obj->type(), OBJ_Line, OBJ_CondLine))
699 continue;
700
701 QVector<LDObjectPtr> newsegs;
702
703 for (int i = 0; i < segments; ++i)
704 {
705 LDObjectPtr segment;
706 Vertex v0, v1;
707
708 v0.apply ([&](Axis ax, double& a)
709 {
710 double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
711 a = (obj->vertex (0)[ax] + ((len * i) / segments));
712 });
713
714 v1.apply ([&](Axis ax, double& a)
715 {
716 double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
717 a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments));
718 });
719
720 if (obj->type() == OBJ_Line)
721 segment = LDSpawn<LDLine> (v0, v1);
722 else
723 segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3));
724
725 newsegs << segment;
726 }
727
728 int ln = obj->lineNumber();
729
730 for (LDObjectPtr seg : newsegs)
731 CurrentDocument()->insertObj (ln++, seg);
732
733 obj->destroy();
734 }
735
736 buildObjList();
737 g_win->refresh();
738 }

mercurial