src/actions/EditActions.cc

changeset 675
450827da2376
parent 629
b75c6cce02e2
child 638
382226e40865
child 681
c1cc036c6e1f
equal deleted inserted replaced
674:3d8ab0f89102 675:450827da2376
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013, 2014 Santeri 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 <QSpinBox>
20 #include <QCheckBox>
21 #include <QBoxLayout>
22 #include <QClipboard>
23 #include "../MainWindow.h"
24 #include "../Main.h"
25 #include "../Document.h"
26 #include "../ColorSelector.h"
27 #include "../Misc.h"
28 #include "../Widgets.h"
29 #include "../GLRenderer.h"
30 #include "../Dialogs.h"
31 #include "../Colors.h"
32 #include "ui_replcoords.h"
33 #include "ui_editraw.h"
34 #include "ui_flip.h"
35 #include "ui_addhistoryline.h"
36
37 cfg (Bool, edit_schemanticinline, false);
38 extern_cfg (String, ld_defaultuser);
39
40 // =============================================================================
41 // -----------------------------------------------------------------------------
42 static int copyToClipboard()
43 {
44 LDObjectList objs = selection();
45 int num = 0;
46
47 // Clear the clipboard first.
48 qApp->clipboard()->clear();
49
50 // Now, copy the contents into the clipboard.
51 QString data;
52
53 for (LDObject* obj : objs)
54 {
55 if (data.length() > 0)
56 data += "\n";
57
58 data += obj->raw();
59 ++num;
60 }
61
62 qApp->clipboard()->setText (data);
63 return num;
64 }
65
66 // =============================================================================
67 // -----------------------------------------------------------------------------
68 DEFINE_ACTION (Cut, CTRL (X))
69 {
70 int num = copyToClipboard();
71 deleteSelection();
72 log (tr ("%1 objects cut"), num);
73 }
74
75 // =============================================================================
76 // -----------------------------------------------------------------------------
77 DEFINE_ACTION (Copy, CTRL (C))
78 {
79 int num = copyToClipboard();
80 log (tr ("%1 objects copied"), num);
81 }
82
83 // =============================================================================
84 // -----------------------------------------------------------------------------
85 DEFINE_ACTION (Paste, CTRL (V))
86 {
87 const QString clipboardText = qApp->clipboard()->text();
88 int idx = getInsertionPoint();
89 getCurrentDocument()->clearSelection();
90 int num = 0;
91
92 for (QString line : clipboardText.split ("\n"))
93 {
94 LDObject* pasted = parseLine (line);
95 getCurrentDocument()->insertObj (idx++, pasted);
96 pasted->select();
97 R()->compileObject (pasted);
98 ++num;
99 }
100
101 log (tr ("%1 objects pasted"), num);
102 refresh();
103 scrollToSelection();
104 }
105
106 // =============================================================================
107 // -----------------------------------------------------------------------------
108 DEFINE_ACTION (Delete, KEY (Delete))
109 {
110 int num = deleteSelection();
111 log (tr ("%1 objects deleted"), num);
112 }
113
114 // =============================================================================
115 // -----------------------------------------------------------------------------
116 static void doInline (bool deep)
117 {
118 LDObjectList sel = selection();
119
120 for (LDObject* obj : sel)
121 {
122 // Get the index of the subfile so we know where to insert the
123 // inlined contents.
124 long idx = obj->getIndex();
125
126 if (idx == -1)
127 continue;
128
129 LDObjectList objs;
130
131 if (obj->getType() == LDObject::ESubfile)
132 objs = static_cast<LDSubfile*> (obj)->inlineContents (
133 (LDSubfile::InlineFlags)
134 ( (deep) ? LDSubfile::DeepInline : 0) |
135 LDSubfile::CacheInline
136 );
137 else
138 continue;
139
140 // Merge in the inlined objects
141 for (LDObject * inlineobj : objs)
142 {
143 QString line = inlineobj->raw();
144 inlineobj->deleteSelf();
145 LDObject* newobj = parseLine (line);
146 getCurrentDocument()->insertObj (idx++, newobj);
147 newobj->select();
148 g_win->R()->compileObject (newobj);
149 }
150
151 // Delete the subfile now as it's been inlined.
152 obj->deleteSelf();
153 }
154
155 g_win->refresh();
156 }
157
158 DEFINE_ACTION (Inline, CTRL (I))
159 {
160 doInline (false);
161 }
162
163 DEFINE_ACTION (InlineDeep, CTRL_SHIFT (I))
164 {
165 doInline (true);
166 }
167
168 // =============================================================================
169 // -----------------------------------------------------------------------------
170 DEFINE_ACTION (SplitQuads, 0)
171 {
172 LDObjectList objs = selection();
173 int num = 0;
174
175 for (LDObject* obj : objs)
176 {
177 if (obj->getType() != LDObject::EQuad)
178 continue;
179
180 // Find the index of this quad
181 long index = obj->getIndex();
182
183 if (index == -1)
184 return;
185
186 QList<LDTriangle*> triangles = static_cast<LDQuad*> (obj)->splitToTriangles();
187
188 // Replace the quad with the first triangle and add the second triangle
189 // after the first one.
190 getCurrentDocument()->setObject (index, triangles[0]);
191 getCurrentDocument()->insertObj (index + 1, triangles[1]);
192
193 for (LDTriangle* t : triangles)
194 R()->compileObject (t);
195
196 // Delete this quad now, it has been split.
197 obj->deleteSelf();
198
199 num++;
200 }
201
202 log ("%1 quadrilaterals split", num);
203 refresh();
204 }
205
206 // =============================================================================
207 // -----------------------------------------------------------------------------
208 DEFINE_ACTION (EditRaw, KEY (F9))
209 {
210 if (selection().size() != 1)
211 return;
212
213 LDObject* obj = selection()[0];
214 QDialog* dlg = new QDialog;
215 Ui::EditRawUI ui;
216
217 ui.setupUi (dlg);
218 ui.code->setText (obj->raw());
219
220 if (obj->getType() == LDObject::EError)
221 ui.errorDescription->setText (static_cast<LDError*> (obj)->reason);
222 else
223 {
224 ui.errorDescription->hide();
225 ui.errorIcon->hide();
226 }
227
228 if (!dlg->exec())
229 return;
230
231 LDObject* oldobj = obj;
232
233 // Reinterpret it from the text of the input field
234 obj = parseLine (ui.code->text());
235 oldobj->replace (obj);
236
237 // Refresh
238 R()->compileObject (obj);
239 refresh();
240 }
241
242 // =============================================================================
243 // -----------------------------------------------------------------------------
244 DEFINE_ACTION (SetColor, KEY (C))
245 {
246 if (selection().isEmpty())
247 return;
248
249 int colnum;
250 int defcol = -1;
251
252 LDObjectList objs = selection();
253
254 // If all selected objects have the same color, said color is our default
255 // value to the color selection dialog.
256 defcol = getSelectedColor();
257
258 // Show the dialog to the user now and ask for a color.
259 if (ColorSelector::selectColor (colnum, defcol, g_win))
260 {
261 for (LDObject* obj : objs)
262 {
263 if (obj->isColored() == false)
264 continue;
265
266 obj->setColor (colnum);
267 R()->compileObject (obj);
268 }
269
270 refresh();
271 }
272 }
273
274 // =============================================================================
275 // -----------------------------------------------------------------------------
276 DEFINE_ACTION (Borders, CTRL_SHIFT (B))
277 {
278 LDObjectList objs = selection();
279 int num = 0;
280
281 for (LDObject* obj : objs)
282 {
283 const LDObject::Type type = obj->getType();
284 if (type != LDObject::EQuad && type != LDObject::ETriangle)
285 continue;
286
287 int numLines;
288 LDLine* lines[4];
289
290 if (type == LDObject::EQuad)
291 {
292 numLines = 4;
293
294 LDQuad* quad = static_cast<LDQuad*> (obj);
295 lines[0] = new LDLine (quad->getVertex (0), quad->getVertex (1));
296 lines[1] = new LDLine (quad->getVertex (1), quad->getVertex (2));
297 lines[2] = new LDLine (quad->getVertex (2), quad->getVertex (3));
298 lines[3] = new LDLine (quad->getVertex (3), quad->getVertex (0));
299 }
300 else
301 {
302 numLines = 3;
303
304 LDTriangle* tri = static_cast<LDTriangle*> (obj);
305 lines[0] = new LDLine (tri->getVertex (0), tri->getVertex (1));
306 lines[1] = new LDLine (tri->getVertex (1), tri->getVertex (2));
307 lines[2] = new LDLine (tri->getVertex (2), tri->getVertex (0));
308 }
309
310 for (int i = 0; i < numLines; ++i)
311 {
312 long idx = obj->getIndex() + i + 1;
313
314 lines[i]->setColor (edgecolor);
315 getCurrentDocument()->insertObj (idx, lines[i]);
316 R()->compileObject (lines[i]);
317 }
318
319 num += numLines;
320 }
321
322 log (tr ("Added %1 border lines"), num);
323 refresh();
324 }
325
326 // =============================================================================
327 // -----------------------------------------------------------------------------
328 DEFINE_ACTION (CornerVerts, 0)
329 {
330 int num = 0;
331
332 for (LDObject* obj : selection())
333 {
334 if (obj->vertices() < 2)
335 continue;
336
337 int idx = obj->getIndex();
338
339 for (int i = 0; i < obj->vertices(); ++i)
340 {
341 LDVertex* vert = new LDVertex;
342 vert->pos = obj->getVertex (i);
343 vert->setColor (obj->getColor());
344
345 getCurrentDocument()->insertObj (++idx, vert);
346 R()->compileObject (vert);
347 ++num;
348 }
349 }
350
351 log (tr ("Added %1 vertices"), num);
352 refresh();
353 }
354
355 // =============================================================================
356 // -----------------------------------------------------------------------------
357 static void doMoveSelection (const bool up)
358 {
359 LDObjectList objs = selection();
360 LDObject::moveObjects (objs, up);
361 g_win->buildObjList();
362 }
363
364 // =============================================================================
365 // -----------------------------------------------------------------------------
366 DEFINE_ACTION (MoveUp, KEY (PageUp))
367 {
368 doMoveSelection (true);
369 }
370
371 DEFINE_ACTION (MoveDown, KEY (PageDown))
372 {
373 doMoveSelection (false);
374 }
375
376 // =============================================================================
377 // -----------------------------------------------------------------------------
378 DEFINE_ACTION (Undo, CTRL (Z))
379 {
380 getCurrentDocument()->undo();
381 }
382
383 DEFINE_ACTION (Redo, CTRL_SHIFT (Z))
384 {
385 getCurrentDocument()->redo();
386 }
387
388 // =============================================================================
389 // -----------------------------------------------------------------------------
390 void doMoveObjects (Vertex vect)
391 {
392 // Apply the grid values
393 vect[X] *= *currentGrid().confs[Grid::X];
394 vect[Y] *= *currentGrid().confs[Grid::Y];
395 vect[Z] *= *currentGrid().confs[Grid::Z];
396
397 for (LDObject* obj : selection())
398 {
399 obj->move (vect);
400 g_win->R()->compileObject (obj);
401 }
402
403 g_win->refresh();
404 }
405
406 // =============================================================================
407 // -----------------------------------------------------------------------------
408 DEFINE_ACTION (MoveXNeg, KEY (Left))
409 {
410 doMoveObjects ({ -1, 0, 0});
411 }
412
413 DEFINE_ACTION (MoveYNeg, KEY (Home))
414 {
415 doMoveObjects ({0, -1, 0});
416 }
417
418 DEFINE_ACTION (MoveZNeg, KEY (Down))
419 {
420 doMoveObjects ({0, 0, -1});
421 }
422
423 DEFINE_ACTION (MoveXPos, KEY (Right))
424 {
425 doMoveObjects ({1, 0, 0});
426 }
427
428 DEFINE_ACTION (MoveYPos, KEY (End))
429 {
430 doMoveObjects ({0, 1, 0});
431 }
432
433 DEFINE_ACTION (MoveZPos, KEY (Up))
434 {
435 doMoveObjects ({0, 0, 1});
436 }
437
438 // =============================================================================
439 // -----------------------------------------------------------------------------
440 DEFINE_ACTION (Invert, CTRL_SHIFT (W))
441 {
442 LDObjectList sel = selection();
443
444 for (LDObject* obj : sel)
445 {
446 obj->invert();
447 R()->compileObject (obj);
448 }
449
450 refresh();
451 }
452
453 // =============================================================================
454 // -----------------------------------------------------------------------------
455 static void rotateVertex (Vertex& v, const Vertex& rotpoint, const Matrix& transform)
456 {
457 v.move (-rotpoint);
458 v.transform (transform, g_origin);
459 v.move (rotpoint);
460 }
461
462 // =============================================================================
463 // -----------------------------------------------------------------------------
464 static void doRotate (const int l, const int m, const int n)
465 {
466 LDObjectList sel = selection();
467 QList<Vertex*> queue;
468 const Vertex rotpoint = rotPoint (sel);
469 const double angle = (pi * *currentGrid().confs[Grid::Angle]) / 180,
470 cosangle = cos (angle),
471 sinangle = sin (angle);
472
473 // ref: http://en.wikipedia.org/wiki/Transformation_matrix#Rotation_2
474 Matrix transform (
475 {
476 (l* l * (1 - cosangle)) + cosangle,
477 (m* l * (1 - cosangle)) - (n* sinangle),
478 (n* l * (1 - cosangle)) + (m* sinangle),
479
480 (l* m * (1 - cosangle)) + (n* sinangle),
481 (m* m * (1 - cosangle)) + cosangle,
482 (n* m * (1 - cosangle)) - (l* sinangle),
483
484 (l* n * (1 - cosangle)) - (m* sinangle),
485 (m* n * (1 - cosangle)) + (l* sinangle),
486 (n* n * (1 - cosangle)) + cosangle
487 });
488
489 // Apply the above matrix to everything
490 for (LDObject* obj : sel)
491 {
492 if (obj->vertices())
493 {
494 for (int i = 0; i < obj->vertices(); ++i)
495 {
496 Vertex v = obj->getVertex (i);
497 rotateVertex (v, rotpoint, transform);
498 obj->setVertex (i, v);
499 }
500 }
501 elif (obj->hasMatrix())
502 {
503 LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
504
505 // Transform the position
506 Vertex v = mo->getPosition();
507 rotateVertex (v, rotpoint, transform);
508 mo->setPosition (v);
509
510 // Transform the matrix
511 mo->setTransform (transform * mo->getTransform());
512 }
513 elif (obj->getType() == LDObject::EVertex)
514 {
515 LDVertex* vert = static_cast<LDVertex*> (obj);
516 Vertex v = vert->pos;
517 rotateVertex (v, rotpoint, transform);
518 vert->pos = v;
519 }
520
521 g_win->R()->compileObject (obj);
522 }
523
524 g_win->refresh();
525 }
526
527 // =============================================================================
528 // -----------------------------------------------------------------------------
529 DEFINE_ACTION (RotateXPos, CTRL (Right))
530 {
531 doRotate (1, 0, 0);
532 }
533 DEFINE_ACTION (RotateYPos, CTRL (End))
534 {
535 doRotate (0, 1, 0);
536 }
537 DEFINE_ACTION (RotateZPos, CTRL (Up))
538 {
539 doRotate (0, 0, 1);
540 }
541 DEFINE_ACTION (RotateXNeg, CTRL (Left))
542 {
543 doRotate (-1, 0, 0);
544 }
545 DEFINE_ACTION (RotateYNeg, CTRL (Home))
546 {
547 doRotate (0, -1, 0);
548 }
549 DEFINE_ACTION (RotateZNeg, CTRL (Down))
550 {
551 doRotate (0, 0, -1);
552 }
553
554 DEFINE_ACTION (RotationPoint, (0))
555 {
556 configRotationPoint();
557 }
558
559 // =============================================================================
560 // -----------------------------------------------------------------------------
561 DEFINE_ACTION (RoundCoordinates, 0)
562 {
563 setlocale (LC_ALL, "C");
564 int num = 0;
565
566 for (LDObject* obj : selection())
567 {
568 LDMatrixObject* mo = dynamic_cast<LDMatrixObject*> (obj);
569
570 if (mo != null)
571 {
572 Vertex v = mo->getPosition();
573 Matrix t = mo->getTransform();
574
575 for_axes (ax)
576 roundToDecimals (v[ax], 3);
577
578 // Let matrix values be rounded to 4 decimals,
579 // they need that extra precision
580 for (int i = 0; i < 9; ++i)
581 roundToDecimals (t[i], 4);
582
583 mo->setPosition (v);
584 mo->setTransform (t);
585 num += 10;
586 }
587 else
588 {
589 for (int i = 0; i < obj->vertices(); ++i)
590 {
591 Vertex v = obj->getVertex (i);
592
593 for_axes (ax)
594 roundToDecimals (v[ax], 3);
595
596 obj->setVertex (i, v);
597 R()->compileObject (obj);
598 num += 3;
599 }
600 }
601 }
602
603 log (tr ("Rounded %1 values"), num);
604 refreshObjectList();
605 refresh();
606 }
607
608 // =============================================================================
609 // -----------------------------------------------------------------------------
610 DEFINE_ACTION (Uncolorize, 0)
611 {
612 int num = 0;
613
614 for (LDObject* obj : selection())
615 {
616 if (obj->isColored() == false)
617 continue;
618
619 int col = maincolor;
620
621 if (obj->getType() == LDObject::ELine || obj->getType() == LDObject::ECondLine)
622 col = edgecolor;
623
624 obj->setColor (col);
625 R()->compileObject (obj);
626 num++;
627 }
628
629 log (tr ("%1 objects uncolored"), num);
630 refresh();
631 }
632
633 // =============================================================================
634 // -----------------------------------------------------------------------------
635 DEFINE_ACTION (ReplaceCoords, CTRL (R))
636 {
637 QDialog* dlg = new QDialog (g_win);
638 Ui::ReplaceCoordsUI ui;
639 ui.setupUi (dlg);
640
641 if (!dlg->exec())
642 return;
643
644 const double search = ui.search->value(),
645 replacement = ui.replacement->value();
646 const bool any = ui.any->isChecked(),
647 rel = ui.relative->isChecked();
648
649 QList<Axis> sel;
650 int num = 0;
651
652 if (ui.x->isChecked()) sel << X;
653 if (ui.y->isChecked()) sel << Y;
654 if (ui.z->isChecked()) sel << Z;
655
656 for (LDObject* obj : selection())
657 {
658 for (int i = 0; i < obj->vertices(); ++i)
659 {
660 Vertex v = obj->getVertex (i);
661
662 for (Axis ax : sel)
663 {
664 double& coord = v[ax];
665
666 if (any || coord == search)
667 {
668 if (!rel)
669 coord = 0;
670
671 coord += replacement;
672 num++;
673 }
674 }
675
676 obj->setVertex (i, v);
677 R()->compileObject (obj);
678 }
679 }
680
681 log (tr ("Altered %1 values"), num);
682 refresh();
683 }
684
685 // =============================================================================
686 // -----------------------------------------------------------------------------
687 DEFINE_ACTION (Flip, CTRL_SHIFT (F))
688 {
689 QDialog* dlg = new QDialog;
690 Ui::FlipUI ui;
691 ui.setupUi (dlg);
692
693 if (!dlg->exec())
694 return;
695
696 QList<Axis> sel;
697
698 if (ui.x->isChecked()) sel << X;
699 if (ui.y->isChecked()) sel << Y;
700 if (ui.z->isChecked()) sel << Z;
701
702 for (LDObject* obj : selection())
703 {
704 for (int i = 0; i < obj->vertices(); ++i)
705 {
706 Vertex v = obj->getVertex (i);
707
708 for (Axis ax : sel)
709 v[ax] *= -1;
710
711 obj->setVertex (i, v);
712 R()->compileObject (obj);
713 }
714 }
715
716 refresh();
717 }
718
719 // =============================================================================
720 // -----------------------------------------------------------------------------
721 DEFINE_ACTION (Demote, 0)
722 {
723 LDObjectList sel = selection();
724 int num = 0;
725
726 for (LDObject* obj : sel)
727 {
728 if (obj->getType() != LDObject::ECondLine)
729 continue;
730
731 LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
732 R()->compileObject (repl);
733 ++num;
734 }
735
736 log (tr ("Demoted %1 conditional lines"), num);
737 refresh();
738 }
739
740 // =============================================================================
741 // -----------------------------------------------------------------------------
742 static bool isColorUsed (int colnum)
743 {
744 for (LDObject* obj : getCurrentDocument()->getObjects())
745 if (obj->isColored() && obj->getColor() == colnum)
746 return true;
747
748 return false;
749 }
750
751 // =============================================================================
752 // -----------------------------------------------------------------------------
753 DEFINE_ACTION (Autocolor, 0)
754 {
755 int colnum = 0;
756
757 while (colnum < MAX_COLORS && (getColor (colnum) == null || isColorUsed (colnum)))
758 colnum++;
759
760 if (colnum >= MAX_COLORS)
761 {
762 log (tr ("Cannot auto-color: all colors are in use!"));
763 return;
764 }
765
766 for (LDObject* obj : selection())
767 {
768 if (obj->isColored() == false)
769 continue;
770
771 obj->setColor (colnum);
772 R()->compileObject (obj);
773 }
774
775 log (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
776 refresh();
777 }
778
779 // =============================================================================
780 // -----------------------------------------------------------------------------
781 DEFINE_ACTION (AddHistoryLine, 0)
782 {
783 LDObject* obj;
784 bool ishistory = false,
785 prevIsHistory = false;
786
787 QDialog* dlg = new QDialog;
788 Ui_AddHistoryLine* ui = new Ui_AddHistoryLine;
789 ui->setupUi (dlg);
790 ui->m_username->setText (ld_defaultuser);
791 ui->m_date->setDate (QDate::currentDate());
792 ui->m_comment->setFocus();
793
794 if (!dlg->exec())
795 return;
796
797 // Create the comment object based on input
798 QString commentText = fmt ("!HISTORY %1 [%2] %3",
799 ui->m_date->date().toString ("yyyy-MM-dd"),
800 ui->m_username->text(),
801 ui->m_comment->text());
802
803 LDComment* comm = new LDComment (commentText);
804
805 // Find a spot to place the new comment
806 for (
807 obj = getCurrentDocument()->getObject (0);
808 obj && obj->next() && !obj->next()->isScemantic();
809 obj = obj->next()
810 )
811 {
812 LDComment* comm = dynamic_cast<LDComment*> (obj);
813
814 if (comm && comm->text.startsWith ("!HISTORY "))
815 ishistory = true;
816
817 if (prevIsHistory && !ishistory)
818 {
819 // Last line was history, this isn't, thus insert the new history
820 // line here.
821 break;
822 }
823
824 prevIsHistory = ishistory;
825 }
826
827 int idx = obj ? obj->getIndex() : 0;
828 getCurrentDocument()->insertObj (idx++, comm);
829
830 // If we're adding a history line right before a scemantic object, pad it
831 // an empty line
832 if (obj && obj->next() && obj->next()->isScemantic())
833 getCurrentDocument()->insertObj (idx, new LDEmpty);
834
835 buildObjList();
836 delete ui;
837 }

mercurial