src/actions/EditActions.cc

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

mercurial