src/gui_editactions.cc

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

mercurial