src/actionsEdit.cpp

changeset 952
f116b63c4844
parent 950
5df69eb50182
child 955
39d789d675fc
equal deleted inserted replaced
950:5df69eb50182 952:f116b63c4844
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013, 2014 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 "dialogs/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 (LDObject* 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::slot_actionCut()
75 {
76 int num = CopyToClipboard();
77 deleteSelection();
78 print (tr ("%1 objects cut"), num);
79 }
80
81 // =============================================================================
82 //
83 void MainWindow::slot_actionCopy()
84 {
85 int num = CopyToClipboard();
86 print (tr ("%1 objects copied"), num);
87 }
88
89 // =============================================================================
90 //
91 void MainWindow::slot_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 LDObject* 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::slot_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(), [&](LDSubfile* 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 (LDObject* inlineobj : objs)
136 {
137 QString line = inlineobj->asText();
138 inlineobj->destroy();
139 LDObject* 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::slot_actionInline()
152 {
153 DoInline (false);
154 }
155
156 void MainWindow::slot_actionInlineDeep()
157 {
158 DoInline (true);
159 }
160
161 // =============================================================================
162 //
163 void MainWindow::slot_actionSplitQuads()
164 {
165 int num = 0;
166
167 LDIterate<LDQuad> (Selection(), [&](LDQuad* 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<LDTriangle*> 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::slot_actionEditRaw()
191 {
192 if (Selection().size() != 1)
193 return;
194
195 LDObject* 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 (static_cast<LDError*> (obj)->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 LDObject* newobj = ParseLine (ui.code->text());
215 obj->replace (newobj);
216 refresh();
217 }
218
219 // =============================================================================
220 //
221 void MainWindow::slot_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 (LDObject* obj : objs)
237 {
238 if (obj->isColored())
239 obj->setColor (color);
240 }
241
242 refresh();
243 }
244 }
245
246 // =============================================================================
247 //
248 void MainWindow::slot_actionBorders()
249 {
250 LDObjectList objs = Selection();
251 int num = 0;
252
253 for (LDObject* obj : objs)
254 {
255 const LDObjectType type = obj->type();
256
257 if (type != OBJ_Quad and type != OBJ_Triangle)
258 continue;
259
260 LDLine* lines[4];
261
262 if (type == OBJ_Quad)
263 {
264 LDQuad* quad = static_cast<LDQuad*> (obj);
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 LDTriangle* tri = static_cast<LDTriangle*> (obj);
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 void MainWindow::slot_actionCornerVerts()
297 {
298 int num = 0;
299
300 for (LDObject* obj : Selection())
301 {
302 if (obj->numVertices() < 2)
303 continue;
304
305 int ln = obj->lineNumber();
306
307 for (int i = 0; i < obj->numVertices(); ++i)
308 {
309 LDVertex* vertex = new LDVertex();
310 vertex->pos = obj->vertex (i);
311 vertex->setColor (obj->color());
312 CurrentDocument()->insertObj (++ln, vertex);
313 ++num;
314 }
315 }
316
317 print (tr ("Added %1 vertices"), num);
318 refresh();
319 }
320
321 // =============================================================================
322 //
323 static void MoveSelection (const bool up)
324 {
325 LDObjectList objs = Selection();
326 LDObject::moveObjects (objs, up);
327 g_win->buildObjList();
328 }
329
330 // =============================================================================
331 //
332 void MainWindow::slot_actionMoveUp()
333 {
334 MoveSelection (true);
335 }
336
337 void MainWindow::slot_actionMoveDown()
338 {
339 MoveSelection (false);
340 }
341
342 // =============================================================================
343 //
344 void MainWindow::slot_actionUndo()
345 {
346 CurrentDocument()->undo();
347 }
348
349 void MainWindow::slot_actionRedo()
350 {
351 CurrentDocument()->redo();
352 }
353
354 // =============================================================================
355 //
356 static void MoveObjects (Vertex vect)
357 {
358 // Apply the grid values
359 vect *= *CurrentGrid().coordinateSnap;
360
361 for (LDObject* obj : Selection())
362 obj->move (vect);
363
364 g_win->refresh();
365 }
366
367 // =============================================================================
368 //
369 void MainWindow::slot_actionMoveXNeg()
370 {
371 MoveObjects ({-1, 0, 0});
372 }
373
374 void MainWindow::slot_actionMoveYNeg()
375 {
376 MoveObjects ({0, -1, 0});
377 }
378
379 void MainWindow::slot_actionMoveZNeg()
380 {
381 MoveObjects ({0, 0, -1});
382 }
383
384 void MainWindow::slot_actionMoveXPos()
385 {
386 MoveObjects ({1, 0, 0});
387 }
388
389 void MainWindow::slot_actionMoveYPos()
390 {
391 MoveObjects ({0, 1, 0});
392 }
393
394 void MainWindow::slot_actionMoveZPos()
395 {
396 MoveObjects ({0, 0, 1});
397 }
398
399 // =============================================================================
400 //
401 void MainWindow::slot_actionInvert()
402 {
403 for (LDObject* obj : Selection())
404 obj->invert();
405
406 refresh();
407 }
408
409 // =============================================================================
410 //
411 static double GetRotateActionAngle()
412 {
413 return (Pi * *CurrentGrid().angleSnap) / 180;
414 }
415
416 void MainWindow::slot_actionRotateXPos()
417 {
418 RotateObjects (1, 0, 0, GetRotateActionAngle(), Selection());
419 }
420 void MainWindow::slot_actionRotateYPos()
421 {
422 RotateObjects (0, 1, 0, GetRotateActionAngle(), Selection());
423 }
424 void MainWindow::slot_actionRotateZPos()
425 {
426 RotateObjects (0, 0, 1, GetRotateActionAngle(), Selection());
427 }
428 void MainWindow::slot_actionRotateXNeg()
429 {
430 RotateObjects (-1, 0, 0, GetRotateActionAngle(), Selection());
431 }
432 void MainWindow::slot_actionRotateYNeg()
433 {
434 RotateObjects (0, -1, 0, GetRotateActionAngle(), Selection());
435 }
436 void MainWindow::slot_actionRotateZNeg()
437 {
438 RotateObjects (0, 0, -1, GetRotateActionAngle(), Selection());
439 }
440
441 void MainWindow::slot_actionRotationPoint()
442 {
443 ConfigureRotationPoint();
444 }
445
446 // =============================================================================
447 //
448 void MainWindow::slot_actionRoundCoordinates()
449 {
450 setlocale (LC_ALL, "C");
451 int num = 0;
452
453 for (LDObject* obj : Selection())
454 {
455 LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
456
457 if (mo != null)
458 {
459 Vertex v = mo->position();
460 Matrix t = mo->transform();
461
462 // Note: matrix values are to be rounded to 4 decimals.
463 v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
464 ApplyToMatrix (t, [](int, double& a) { RoundToDecimals (a, cfg::RoundMatrix); });
465
466 mo->setPosition (v);
467 mo->setTransform (t);
468 num += 12;
469 }
470 else
471 {
472 for (int i = 0; i < obj->numVertices(); ++i)
473 {
474 Vertex v = obj->vertex (i);
475 v.apply ([](Axis, double& a) { RoundToDecimals (a, cfg::RoundPosition); });
476 obj->setVertex (i, v);
477 num += 3;
478 }
479 }
480 }
481
482 print (tr ("Rounded %1 values"), num);
483 refreshObjectList();
484 refresh();
485 }
486
487 // =============================================================================
488 //
489 void MainWindow::slot_actionUncolor()
490 {
491 int num = 0;
492
493 for (LDObject* obj : Selection())
494 {
495 if (not obj->isColored())
496 continue;
497
498 obj->setColor (obj->defaultColor());
499 num++;
500 }
501
502 print (tr ("%1 objects uncolored"), num);
503 refresh();
504 }
505
506 // =============================================================================
507 //
508 void MainWindow::slot_actionReplaceCoords()
509 {
510 QDialog* dlg = new QDialog (g_win);
511 Ui::ReplaceCoordsUI ui;
512 ui.setupUi (dlg);
513
514 if (not dlg->exec())
515 return;
516
517 const double search = ui.search->value(),
518 replacement = ui.replacement->value();
519 const bool any = ui.any->isChecked(),
520 rel = ui.relative->isChecked();
521
522 QList<Axis> sel;
523 int num = 0;
524
525 if (ui.x->isChecked()) sel << X;
526 if (ui.y->isChecked()) sel << Y;
527 if (ui.z->isChecked()) sel << Z;
528
529 for (LDObject* obj : Selection())
530 {
531 for (int i = 0; i < obj->numVertices(); ++i)
532 {
533 Vertex v = obj->vertex (i);
534
535 v.apply ([&](Axis ax, double& coord)
536 {
537 if (not sel.contains (ax) or
538 (not any and coord != search))
539 {
540 return;
541 }
542
543 if (not rel)
544 coord = 0;
545
546 coord += replacement;
547 num++;
548 });
549
550 obj->setVertex (i, v);
551 }
552 }
553
554 print (tr ("Altered %1 values"), num);
555 refresh();
556 }
557
558 // =============================================================================
559 //
560 void MainWindow::slot_actionFlip()
561 {
562 QDialog* dlg = new QDialog;
563 Ui::FlipUI ui;
564 ui.setupUi (dlg);
565
566 if (not dlg->exec())
567 return;
568
569 QList<Axis> sel;
570
571 if (ui.x->isChecked()) sel << X;
572 if (ui.y->isChecked()) sel << Y;
573 if (ui.z->isChecked()) sel << Z;
574
575 for (LDObject* obj : Selection())
576 {
577 for (int i = 0; i < obj->numVertices(); ++i)
578 {
579 Vertex v = obj->vertex (i);
580
581 v.apply ([&](Axis ax, double& a)
582 {
583 if (sel.contains (ax))
584 a = -a;
585 });
586
587 obj->setVertex (i, v);
588 }
589 }
590
591 refresh();
592 }
593
594 // =============================================================================
595 //
596 void MainWindow::slot_actionDemote()
597 {
598 int num = 0;
599
600 LDIterate<LDCondLine> (Selection(), [&](LDCondLine* const& cnd)
601 {
602 cnd->toEdgeLine();
603 ++num;
604 });
605
606 print (tr ("Demoted %1 conditional lines"), num);
607 refresh();
608 }
609
610 // =============================================================================
611 //
612 static bool IsColorUsed (LDColor color)
613 {
614 for (LDObject* obj : CurrentDocument()->objects())
615 {
616 if (obj->isColored() and obj->color() == color)
617 return true;
618 }
619
620 return false;
621 }
622
623 // =============================================================================
624 //
625 void MainWindow::slot_actionAutocolor()
626 {
627 LDColor color;
628
629 for (color = 0; color.isLDConfigColor(); ++color)
630 {
631 if (color.isValid() and not IsColorUsed (color))
632 break;
633 }
634
635 if (not color.isLDConfigColor())
636 {
637 print (tr ("Cannot auto-color: all colors are in use!"));
638 return;
639 }
640
641 for (LDObject* obj : Selection())
642 {
643 if (not obj->isColored())
644 continue;
645
646 obj->setColor (color);
647 }
648
649 print (tr ("Auto-colored: new color is [%1] %2"), color.index(), color.name());
650 refresh();
651 }
652
653 // =============================================================================
654 //
655 void MainWindow::slot_actionAddHistoryLine()
656 {
657 LDObject* obj;
658 bool ishistory = false;
659 bool prevIsHistory = false;
660
661 QDialog* dlg = new QDialog;
662 Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
663 ui->setupUi (dlg);
664 ui->m_username->setText (cfg::DefaultUser);
665 ui->m_date->setDate (QDate::currentDate());
666 ui->m_comment->setFocus();
667
668 if (not dlg->exec())
669 return;
670
671 // Create the comment object based on input
672 LDComment* comment = new LDComment (format ("!HISTORY %1 [%2] %3",
673 ui->m_date->date().toString ("yyyy-MM-dd"),
674 ui->m_username->text(),
675 ui->m_comment->text()));
676
677 // Find a spot to place the new comment
678 for (obj = CurrentDocument()->getObject (0);
679 obj and obj->next() and not obj->next()->isScemantic();
680 obj = obj->next())
681 {
682 LDComment* comment = dynamic_cast<LDComment*> (obj);
683
684 if (comment and comment->text().startsWith ("!HISTORY "))
685 ishistory = true;
686
687 if (prevIsHistory and not ishistory)
688 break; // Last line was history, this isn't, thus insert the new history line here.
689
690 prevIsHistory = ishistory;
691 }
692
693 int idx = obj ? obj->lineNumber() : 0;
694 CurrentDocument()->insertObj (idx++, comment);
695
696 // If we're adding a history line right before a scemantic object, pad it
697 // an empty line
698 if (obj and obj->next() and obj->next()->isScemantic())
699 CurrentDocument()->insertObj (idx, new LDEmpty);
700
701 buildObjList();
702 delete ui;
703 }
704
705 void MainWindow::slot_actionSplitLines()
706 {
707 bool ok;
708 int segments = QInputDialog::getInt (g_win, APPNAME, "Amount of segments:", cfg::SplitLinesSegments, 0,
709 std::numeric_limits<int>::max(), 1, &ok);
710
711 if (not ok)
712 return;
713
714 cfg::SplitLinesSegments = segments;
715
716 for (LDObject* obj : Selection())
717 {
718 if (not Eq (obj->type(), OBJ_Line, OBJ_CondLine))
719 continue;
720
721 QVector<LDObject*> newsegs;
722
723 for (int i = 0; i < segments; ++i)
724 {
725 LDObject* segment;
726 Vertex v0, v1;
727
728 v0.apply ([&](Axis ax, double& a)
729 {
730 double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
731 a = (obj->vertex (0)[ax] + ((len * i) / segments));
732 });
733
734 v1.apply ([&](Axis ax, double& a)
735 {
736 double len = obj->vertex (1)[ax] - obj->vertex (0)[ax];
737 a = (obj->vertex (0)[ax] + ((len * (i + 1)) / segments));
738 });
739
740 if (obj->type() == OBJ_Line)
741 segment = LDSpawn<LDLine> (v0, v1);
742 else
743 segment = LDSpawn<LDCondLine> (v0, v1, obj->vertex (2), obj->vertex (3));
744
745 newsegs << segment;
746 }
747
748 int ln = obj->lineNumber();
749
750 for (LDObject* seg : newsegs)
751 CurrentDocument()->insertObj (ln++, seg);
752
753 obj->destroy();
754 }
755
756 buildObjList();
757 g_win->refresh();
758 }

mercurial