]> Shamusworld >> Repos - architektonas/blob - src/applicationwindow.cpp
GUI functionality fixes.
[architektonas] / src / applicationwindow.cpp
1 //
2 // applicationwindow.cpp: Architektonas
3 //
4 // Part of the Architektonas Project
5 // (C) 2011 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // Who  When        What
11 // ---  ----------  ------------------------------------------------------------
12 // JLH  03/22/2011  Created this file
13 // JLH  09/29/2011  Added simple zoom in/out functionality
14 // JLH  10/03/2011  Fixed zoom tool to zoom in/out from center of screen
15 //
16
17 // FIXED:
18 //
19 //
20 // STILL TO BE DONE:
21 //
22 //
23
24 // Uncomment this for debugging...
25 //#define DEBUG
26 //#define DEBUGFOO                      // Various tool debugging...
27 //#define DEBUGTP                               // Toolpalette debugging...
28
29 #include "applicationwindow.h"
30
31 #include "about.h"
32 #include "blockwidget.h"
33 #include "drawingview.h"
34 #include "fileio.h"
35 #include "generaltab.h"
36 #include "global.h"
37 #include "layerwidget.h"
38 #include "objectwidget.h"
39 #include "painter.h"
40 #include "penwidget.h"
41 #include "settingsdialog.h"
42 #include "structs.h"
43 #include "utils.h"
44
45
46 // Class variables
47 DrawingView * ApplicationWindow::drawing;
48
49
50 ApplicationWindow::ApplicationWindow():
51         baseUnitInput(new QLineEdit),
52         dimensionSizeInput(new QLineEdit),
53         settings("Underground Software", "Architektonas")
54 {
55         drawing = new DrawingView(this);
56         drawing->setMouseTracking(true);                // We want *all* mouse events...!
57         drawing->setFocusPolicy(Qt::StrongFocus);
58         setCentralWidget(drawing);
59
60         aboutWin = new AboutWindow(this);
61
62 //      ((TTEdit *)qApp)->charWnd = new CharWindow(this);
63
64         setWindowIcon(QIcon(":/res/atns-icon.png"));
65         setWindowTitle("Architektonas");
66
67         CreateActions();
68         CreateMenus();
69         CreateToolbars();
70
71         // Create Dock widgets
72         QDockWidget * dock1 = new QDockWidget(tr("Layers"), this);
73         LayerWidget * lw = new LayerWidget;
74         dock1->setWidget(lw);
75         addDockWidget(Qt::RightDockWidgetArea, dock1);
76         QDockWidget * dock2 = new QDockWidget(tr("Blocks"), this);
77         BlockWidget * bw = new BlockWidget;
78         dock2->setWidget(bw);
79         addDockWidget(Qt::RightDockWidgetArea, dock2);
80         QDockWidget * dock3 = new QDockWidget(tr("Object"), this);
81         ObjectWidget * ow = new ObjectWidget;
82         dock3->setWidget(ow);
83         addDockWidget(Qt::RightDockWidgetArea, dock3);
84         // Needed for saveState()
85         dock1->setObjectName("Layers");
86         dock2->setObjectName("Blocks");
87         dock3->setObjectName("Object");
88
89         // Create status bar
90         zoomIndicator = new QLabel("Grid: 12.0\" BU: Inch");
91         statusBar()->addPermanentWidget(zoomIndicator);
92         statusBar()->showMessage(tr("Ready"));
93
94         ReadSettings();
95         setUnifiedTitleAndToolBarOnMac(true);
96         Global::font =  new QFont("Verdana", 15, QFont::Bold);
97
98         connect(lw, SIGNAL(LayerDeleted(int)), drawing, SLOT(DeleteCurrentLayer(int)));
99         connect(lw, SIGNAL(LayerToggled()), drawing, SLOT(HandleLayerToggle()));
100         connect(lw, SIGNAL(LayersSwapped(int, int)), drawing, SLOT(HandleLayerSwap(int, int)));
101         connect(this, SIGNAL(ReloadLayers()), lw, SLOT(Reload()));
102
103         connect(drawing, SIGNAL(ObjectHovered(Object *)), ow, SLOT(ShowInfo(Object *)));
104 }
105
106
107 void ApplicationWindow::closeEvent(QCloseEvent * event)
108 {
109         WriteSettings();
110         event->accept();                // Use ignore() if can't close for some reason
111         //Do we have a memory leak here if we don't delete the font in the Object???
112 }
113
114
115 void ApplicationWindow::FileNew(void)
116 {
117         // Warn the user if drawing has changed and hasn't been saved...
118         if (drawing->dirty)
119         {
120                 QMessageBox msg;
121
122                 msg.setText("The document has been modified.");
123                 msg.setInformativeText("Do you want to save your changes?");
124                 msg.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
125                 msg.setDefaultButton(QMessageBox::Save);
126                 int response = msg.exec();
127
128                 switch (response)
129                 {
130                 case QMessageBox::Save:
131                         // Save was clicked
132                         FileSave();
133                         break;
134                 case QMessageBox::Discard:
135                         // Don't Save was clicked
136                         break;
137                 case QMessageBox::Cancel:
138                         // Cancel was clicked
139                         return;
140 //                      break;
141                 }
142         }
143
144         FileIO::ResetLayerVectors();
145         emit ReloadLayers();
146         DeleteContents(drawing->document.objects);
147         drawing->document.objects.clear();
148         drawing->dirty = false;
149         drawing->update();
150         documentName.clear();
151         setWindowTitle("Architektonas - Untitled");
152         statusBar()->showMessage(tr("New drawing is ready."));
153 }
154
155
156 void ApplicationWindow::FileOpen(void)
157 {
158         QString filename = QFileDialog::getOpenFileName(this, tr("Open Drawing"),
159                 "", tr("Architektonas files (*.drawing)"));
160
161         // User cancelled open
162         if (filename.isEmpty())
163                 return;
164
165         FILE * file = fopen(filename.toUtf8().data(), "r");
166
167         if (file == 0)
168         {
169                 QMessageBox msg;
170                 msg.setText(QString(tr("Could not open file \"%1\" for loading!")).arg(filename));
171                 msg.setIcon(QMessageBox::Critical);
172                 msg.exec();
173                 return;
174         }
175
176         Container container(true);      // Make sure it's a top level container...
177         bool successful = FileIO::LoadAtnsFile(file, &container);
178         fclose(file);
179
180         if (!successful)
181         {
182                 QMessageBox msg;
183                 msg.setText(QString(tr("Could not load file \"%1\"!")).arg(filename));
184                 msg.setIcon(QMessageBox::Critical);
185                 msg.exec();
186                 // Make sure to delete any hanging objects in the container...
187                 DeleteContents(container.objects);
188                 return;
189         }
190
191 //printf("FileOpen: container size = %li\n", container.objects.size());
192         // Keep memory leaks from happening by getting rid of the old document
193         DeleteContents(drawing->document.objects);
194         emit ReloadLayers();
195         // We can do this because the vector is just a bunch of pointers to our
196         // Objects, and the Containers (non-empty) can be moved this way with no
197         // problem.
198         drawing->document = container;
199         drawing->dirty = false;
200         drawing->update();
201         documentName = filename;
202         setWindowTitle(QString("Architektonas - %1").arg(documentName));
203         statusBar()->showMessage(tr("Drawing loaded."));
204 }
205
206
207 void ApplicationWindow::FileSave(void)
208 {
209         if (documentName.isEmpty())
210                 documentName = QFileDialog::getSaveFileName(this, tr("Save Drawing"),
211                         "", tr("Architektonas drawings (*.drawing)"));
212
213         FILE * file = fopen(documentName.toUtf8().data(), "w");
214
215         if (file == 0)
216         {
217                 QMessageBox msg;
218                 msg.setText(QString(tr("Could not open file \"%1\" for saving!")).arg(documentName));
219                 msg.setIcon(QMessageBox::Critical);
220                 msg.exec();
221                 return;
222         }
223
224         bool successful = FileIO::SaveAtnsFile(file, &drawing->document);
225         fclose(file);
226
227         if (!successful)
228         {
229                 QMessageBox msg;
230                 msg.setText(QString(tr("Could not save file \"%1\"!")).arg(documentName));
231                 msg.setIcon(QMessageBox::Critical);
232                 msg.exec();
233                 // In this case, we should unlink the created file, since it's not
234                 // right...
235 //              unlink(documentName.toUtf8().data());
236                 QFile::remove(documentName);
237                 return;
238         }
239
240         drawing->dirty = false;
241         setWindowTitle(QString("Architektonas - %1").arg(documentName));
242         statusBar()->showMessage(tr("Drawing saved."));
243 }
244
245
246 void ApplicationWindow::FileSaveAs(void)
247 {
248         QString filename = QFileDialog::getSaveFileName(this, tr("Save Drawing As"),
249                 documentName, tr("Architektonas drawings (*.drawing)"));
250
251         if (!filename.isEmpty())
252         {
253                 documentName = filename;
254                 FileSave();
255         }
256 }
257
258
259 void ApplicationWindow::contextMenuEvent(QContextMenuEvent * event)
260 {
261         QMenu menu(this);
262         menu.addAction(mirrorAct);
263         menu.addAction(rotateAct);
264         menu.addAction(trimAct);
265         menu.exec(event->globalPos());
266 }
267
268
269 void ApplicationWindow::SnapToGridTool(void)
270 {
271         Global::snapToGrid = snapToGridAct->isChecked();
272 }
273
274
275 void ApplicationWindow::FixAngle(void)
276 {
277         Global::fixedAngle = fixAngleAct->isChecked();
278 }
279
280
281 void ApplicationWindow::FixLength(void)
282 {
283         Global::fixedLength = fixLengthAct->isChecked();
284 }
285
286
287 void ApplicationWindow::DeleteTool(void)
288 {
289         // For this tool, we check first to see if anything is selected. If so, we
290         // delete those and *don't* select the delete tool.
291 //      if (drawing->numSelected > 0)
292         if (drawing->select.size() > 0)
293         {
294                 DeleteSelectedObjects(drawing->document.objects);
295                 drawing->update();
296                 deleteAct->setChecked(false);
297                 return;
298         }
299
300         // Otherwise, toggle the state of the tool
301         ClearUIToolStatesExcept(deleteAct);
302         SetInternalToolStates();
303 }
304
305
306 void ApplicationWindow::DimensionTool(void)
307 {
308         ClearUIToolStatesExcept(addDimensionAct);
309         SetInternalToolStates();
310 }
311
312
313 void ApplicationWindow::RotateTool(void)
314 {
315         ClearUIToolStatesExcept(rotateAct);
316
317         // Do tear-down if Rotate tool has been turned off
318         if (!rotateAct->isChecked())
319                 drawing->RotateHandler(ToolCleanup, Point(0, 0));
320
321         SetInternalToolStates();
322 }
323
324
325 void ApplicationWindow::MirrorTool(void)
326 {
327         ClearUIToolStatesExcept(mirrorAct);
328
329         // Do tear-down if Rotate tool has been turned off
330         if (!mirrorAct->isChecked())
331                 drawing->MirrorHandler(ToolCleanup, Point(0, 0));
332
333         SetInternalToolStates();
334 }
335
336
337 void ApplicationWindow::TrimTool(void)
338 {
339         ClearUIToolStatesExcept(trimAct);
340         SetInternalToolStates();
341 }
342
343
344 void ApplicationWindow::TriangulateTool(void)
345 {
346         ClearUIToolStatesExcept(triangulateAct);
347         SetInternalToolStates();
348 }
349
350
351 void ApplicationWindow::AddLineTool(void)
352 {
353         ClearUIToolStatesExcept(addLineAct);
354         SetInternalToolStates();
355 }
356
357
358 void ApplicationWindow::AddCircleTool(void)
359 {
360         ClearUIToolStatesExcept(addCircleAct);
361         SetInternalToolStates();
362 }
363
364
365 void ApplicationWindow::AddArcTool(void)
366 {
367         ClearUIToolStatesExcept(addArcAct);
368         SetInternalToolStates();
369 }
370
371
372 void ApplicationWindow::AddPolygonTool(void)
373 {
374         ClearUIToolStatesExcept(addPolygonAct);
375         SetInternalToolStates();
376 }
377
378
379 void ApplicationWindow::AddSplineTool(void)
380 {
381         ClearUIToolStatesExcept(addSplineAct);
382         SetInternalToolStates();
383 }
384
385
386 void ApplicationWindow::ZoomInTool(void)
387 {
388         double zoomFactor = 2.0;
389 /*
390 We need to find the center of the screen, then figure out where the new corner
391 will be in the zoomed in window.
392
393 So we know in Qt coords, the center is found via:
394 size.width()  / 2 --> xCenter
395 size.height() / 2 --> yCenter
396
397 transform x/yCenter to Cartesian coordinates. So far, so good.
398
399 when zooming in, new origin will be (xCenter - origin.x) / 2, (yCenter - origin.y) / 2
400 (after subtracting from center, that is...)
401 */
402         QSize size = drawing->size();
403         Vector center(size.width() / 2.0, size.height() / 2.0);
404 //printf("Zoom in... Center=%.2f,%.2f; ", center.x, center.y);
405         center = Painter::QtToCartesianCoords(center);
406 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
407         Vector newOrigin = center - ((center - Global::origin) / zoomFactor);
408 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
409         Global::origin = newOrigin;
410
411 //printf("Zoom in... level going from %02f to ", Painter::zoom);
412         // This just zooms leaving origin intact... should zoom in at the current
413         // center! [DONE]
414         Global::zoom *= zoomFactor;
415         Global::gridSpacing = drawing->gridPixels / Global::zoom;
416         drawing->UpdateGridBackground();
417         drawing->update();
418
419         zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
420         baseUnitInput->setText(QString("%1").arg(Global::gridSpacing));
421 }
422
423
424 void ApplicationWindow::ZoomOutTool(void)
425 {
426 /*
427 Ok, real example.
428 center = (436, 311)
429 origin = (223, 160.5)
430 newOrigin should be (-10, -10)
431 Why isn't it?
432
433 center - origin = (213, 150.5)
434 origin - center = (-213, -150.5)
435 x 2 = (-426, -301)
436 + center = (-10, -10)
437
438 */
439         double zoomFactor = 2.0;
440         QSize size = drawing->size();
441         Vector center(size.width() / 2.0, size.height() / 2.0);
442 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
443         center = Painter::QtToCartesianCoords(center);
444 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
445 //      Vector newOrigin = (center - Painter::origin) * zoomFactor;
446 //      Vector newOrigin = center - (Painter::origin * zoomFactor);
447         Vector newOrigin = center + ((Global::origin - center) * zoomFactor);
448 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
449         Global::origin = newOrigin;
450 //printf("Zoom out...\n");
451         // This just zooms leaving origin intact... should zoom out at the current
452         // center! [DONE]
453         Global::zoom /= zoomFactor;
454         Global::gridSpacing = drawing->gridPixels / Global::zoom;
455         drawing->UpdateGridBackground();
456         drawing->update();
457
458         zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(Global::gridSpacing));
459         baseUnitInput->setText(QString("%1").arg(Global::gridSpacing));
460 }
461
462
463 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
464 {
465         QAction * actionList[] = {
466                 addArcAct, addLineAct, addCircleAct, addDimensionAct, addPolygonAct,
467                 addSplineAct, deleteAct, rotateAct, mirrorAct, trimAct, triangulateAct, 0
468         };
469
470         for(int i=0; actionList[i]!=0; i++)
471         {
472                 if (actionList[i] != exception)
473                         actionList[i]->setChecked(false);
474         }
475 }
476
477
478 void ApplicationWindow::SetInternalToolStates(void)
479 {
480         // We can be sure that if we've come here, then either an active tool is
481         // being deactivated, or a new tool is being created. In either case, the
482         // old tool needs to be deleted.
483         Global::toolState = TSNone;
484
485         if (addLineAct->isChecked())
486                 Global::tool = TTLine;
487         else if (addCircleAct->isChecked())
488                 Global::tool = TTCircle;
489         else if (addArcAct->isChecked())
490                 Global::tool = TTArc;
491         else if (addDimensionAct->isChecked())
492                 Global::tool = TTDimension;
493         else if (addSplineAct->isChecked())
494                 Global::tool = TTSpline;
495         else if (addPolygonAct->isChecked())
496                 Global::tool = TTPolygon;
497         else if (deleteAct->isChecked())
498                 Global::tool = TTDelete;
499         else if (mirrorAct->isChecked())
500                 Global::tool = TTMirror;
501         else if (rotateAct->isChecked())
502                 Global::tool = TTRotate;
503         else if (trimAct->isChecked())
504                 Global::tool = TTTrim;
505         else if (triangulateAct->isChecked())
506                 Global::tool = TTTriangulate;
507         else
508                 Global::tool = TTNone;
509
510         drawing->update();
511 }
512
513
514 void ApplicationWindow::HelpAbout(void)
515 {
516         aboutWin->show();
517 }
518
519
520 void ApplicationWindow::Settings(void)
521 {
522         SettingsDialog dlg(this);
523         dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
524
525         if (dlg.exec() == false)
526                 return;
527
528         // Deal with stuff here (since user hit "OK" button...)
529         drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
530         WriteSettings();
531 }
532
533
534 //
535 // Group a bunch of selected objects (which can include other groups) together
536 // or ungroup a selected group.
537 //
538 void ApplicationWindow::HandleGrouping(void)
539 {
540         int numSelected = drawing->select.size();
541
542         // If nothing selected, do nothing
543         if (numSelected == 0)
544         {
545                 statusBar()->showMessage(tr("No objects selected to make a group from."));
546                 return;
547         }
548
549         // If it's a group that's selected, ungroup it and leave the objects in a
550         // selected state
551         if (numSelected == 1)
552         {
553                 Object * obj = (Object *)drawing->select[0];
554
555                 if (obj->type != OTContainer)
556                 {
557                         statusBar()->showMessage(tr("A group requires two or more selected objects."));
558                         return;
559                 }
560
561                 // Need the parent of the group, we're assuming here that the parent is
562                 // the drawing's document. Does it matter? Maybe...
563                 // Could just stipulate that grouping like this only takes place where
564                 // the parent of the group is the drawing's document. Makes life much
565                 // simpler.
566                 Container * c = (Container *)obj;
567                 SelectAll(c->objects);
568                 RemoveSelectedObjects(drawing->document.objects);
569                 AddObjectsTo(drawing->document.objects, c->objects);
570                 drawing->select.clear();
571                 AddObjectsTo(drawing->select, c->objects);
572                 delete c;
573                 statusBar()->showMessage(tr("Objects ungrouped."));
574 //printf("Ungroup: document size = %li\n", drawing->document.objects.size());
575         }
576         // Otherwise, if it's a group of 2 or more objects (which can be groups too)
577         // group them and select the group
578         else if (numSelected > 1)
579         {
580                 Container * c = new Container();
581                 MoveSelectedObjectsTo(c->objects, drawing->document.objects);
582                 drawing->document.objects.push_back(c);
583                 ClearSelected(c->objects);
584                 c->selected = true;
585                 c->layer = Global::activeLayer;
586
587                 Rect r = drawing->GetObjectExtents((Object *)c);
588                 c->p[0] = r.TopLeft();
589                 c->p[1] = r.BottomRight();
590
591                 drawing->select.clear();
592                 drawing->select.push_back(c);
593                 statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(numSelected));
594 //printf("Group: document size = %li\n", drawing->document.objects.size());
595         }
596
597         drawing->update();
598 }
599
600
601 void ApplicationWindow::HandleConnection(void)
602 {
603 #if 0
604 //double tt = Geometry::ParameterOfLineAndPoint(Vector(0, 0), Vector(10, 0), Vector(8, 2));
605 //printf("Parameter of point @ (8,2) of line (0,0), (10,0): %lf\n", tt);
606         int itemsSelected = drawing->document.ItemsSelected();
607
608         // If nothing selected, do nothing
609         if (itemsSelected == 0)
610         {
611                 statusBar()->showMessage(tr("No objects selected to connect."));
612                 return;
613         }
614
615         // If one thing selected, do nothing
616         if (itemsSelected == 1)
617         {
618                 statusBar()->showMessage(tr("Nothing to connect object to."));
619                 return;
620         }
621
622         // This is O(n^2 / 2) :-P
623         for(int i=0; i<itemsSelected; i++)
624         {
625                 Object * obj1 = drawing->document.SelectedItem(i);
626
627                 for(int j=i+1; j<itemsSelected; j++)
628                 {
629                         Object * obj2 = drawing->document.SelectedItem(j);
630                         double t, u, v, w;
631
632 //                      if ((obj1->type != OTLine) || (obj2->type != OTLine))
633 //                              continue;
634
635                         if ((obj1->type == OTLine) && (obj2->type == OTLine))
636                         {
637 //printf("Testing objects for intersection (%X, %X)...\n", obj1, obj2);
638                                 int intersects = Geometry::Intersects((Line *)obj1, (Line *)obj2, &t, &u);
639 //printf("  (%s) --> t=%lf, u=%lf\n", (intersects ? "true" : "FALSE"), t, u);
640
641                                 if (intersects)
642                                 {
643         //printf("Connecting objects (%X, %X)...\n", obj1, obj2);
644                                         obj1->Connect(obj2, u);
645                                         obj2->Connect(obj1, t);
646                                 }
647                         }
648                         else if (((obj1->type == OTLine) && (obj2->type == OTDimension))
649                                 || ((obj2->type == OTLine) && (obj1->type == OTDimension)))
650                         {
651 printf("Testing Line<->Dimension intersection...\n");
652                                 Line * line = (Line *)(obj1->type == OTLine ? obj1 : obj2);
653                                 Dimension * dim = (Dimension *)(obj1->type == OTDimension ? obj1 : obj2);
654
655                                 int intersects = Geometry::Intersects(line, dim, &t, &u);
656 printf("   -> intersects = %i, t=%lf, u=%lf\n", intersects, t, u);
657
658                                 if (intersects)
659                                 {
660                                         obj1->Connect(obj2, u);
661                                         obj2->Connect(obj1, t);
662                                 }
663                         }
664                 }
665         }
666 #else
667 #endif
668 }
669
670
671 void ApplicationWindow::HandleDisconnection(void)
672 {
673 }
674
675
676 void ApplicationWindow::HandleGridSizeInPixels(int size)
677 {
678         drawing->SetGridSize(size);
679         drawing->update();
680 }
681
682
683 void ApplicationWindow::HandleGridSizeInBaseUnits(QString text)
684 {
685         // Parse the text...
686         bool ok;
687         double value = text.toDouble(&ok);
688
689         // Nothing parsable to a double, so quit...
690         if (!ok || value == 0)
691                 return;
692
693 //      drawing->gridSpacing = value;
694 //      Painter::zoom = drawing->gridPixels / drawing->gridSpacing;
695         Global::gridSpacing = value;
696         Global::zoom = drawing->gridPixels / Global::gridSpacing;
697         drawing->UpdateGridBackground();
698         drawing->update();
699 }
700
701
702 void ApplicationWindow::HandleDimensionSize(QString text)
703 {
704 /*
705 This is the third input on the view toolbar; not sure what it was supposed to do... (resize all dimensions in the drawing?)
706 */
707         // Parse the text...
708         bool ok;
709         double value = text.toDouble(&ok);
710
711         // Nothing parsable to a double, so quit...
712         if (!ok || value == 0)
713                 return;
714
715 //      drawing->document.ResizeAllDimensions(value);
716         drawing->update();
717 }
718
719 void ApplicationWindow::EditCopy(void)
720 {
721         if (drawing->select.size() > 0)
722         {
723                 DeleteContents(clipboard);
724                 clipboard = CopySelectedObjects(drawing->document.objects);
725         }
726 }
727
728
729 void ApplicationWindow::EditCut(void)
730 {
731         if (drawing->select.size() > 0)
732         {
733                 DeleteContents(clipboard);
734                 clipboard = MoveSelectedObjectsFrom(drawing->document.objects);
735                 drawing->update();
736         }
737 }
738
739
740 void ApplicationWindow::EditPaste(void)
741 {
742         if (clipboard.size() > 0)
743         {
744                 // We want to maybe make it so that the pasted objects are being moved in a "mouse drag" state...
745                 ClearSelected(drawing->document.objects);
746                 SelectAll(clipboard);
747                 drawing->document.Add(CopyObjects(clipboard));
748                 drawing->update();
749         }
750 }
751
752
753 //
754 // Select all *visible* objects.  If an object is on a layer that is not
755 // visible, skip it.
756 //
757 void ApplicationWindow::SelectAllObjects(void)
758 {
759         // Set object's state & update the drawing
760         SelectAll(drawing->document.objects);
761         drawing->update();
762 }
763
764
765 void ApplicationWindow::CreateActions(void)
766 {
767         exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
768                 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
769         connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
770
771         snapToGridAct = CreateAction(tr("Snap To &Grid"), tr("Snap To Grid"), tr("Snaps mouse cursor to the visible grid when moving/creating objects."), QIcon(":/res/snap-to-grid-tool.png"), QKeySequence(tr("S")), true);
772         connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
773
774         fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
775                 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
776         connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
777
778         fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
779                 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
780         connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
781
782         deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(tr("Delete")), true);
783         connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
784
785         addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
786         connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
787
788         addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
789         connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
790
791         addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
792         connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
793
794         addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
795         connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
796
797         addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
798         connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
799
800         addSplineAct = CreateAction(tr("Add &Spline"), tr("Add Spline"), tr("Add a NURB spline to the drawing."), QIcon(":/res/add-spline-tool.png"), QKeySequence("A,S"), true);
801         connect(addSplineAct, SIGNAL(triggered()), this, SLOT(AddSplineTool()));
802
803         aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
804         connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
805
806         rotateAct = CreateAction(tr("&Rotate Objects"), tr("Rotate"), tr("Rotate object(s) around an arbitrary center."), QIcon(":/res/rotate-tool.png"), QKeySequence(tr("R,O")), true);
807         connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
808
809         zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
810         connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
811
812         zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
813         connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
814
815         fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/file-new.png"), QKeySequence(tr("Ctrl+n")));
816         connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
817
818         fileOpenAct = CreateAction(tr("&Open Drawing"), tr("Open Drawing"), tr("Opens an existing drawing from a file."), QIcon(":/res/file-open.png"), QKeySequence(tr("Ctrl+o")));
819         connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
820
821         fileSaveAct = CreateAction(tr("&Save Drawing"), tr("Save Drawing"), tr("Saves the current drawing to a file."), QIcon(":/res/file-save.png"), QKeySequence(tr("Ctrl+s")));
822         connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
823
824         fileSaveAsAct = CreateAction(tr("Save Drawing &As"), tr("Save As"), tr("Saves the current drawing to a file with a different name."), QIcon(":/res/file-save-as.png"), QKeySequence(tr("Ctrl+Shift+s")));
825         connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
826
827         fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/file-close.png"), QKeySequence(tr("Ctrl+w")));
828
829         settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/settings.png"), QKeySequence());
830         connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
831
832         groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g"));
833         connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
834
835         connectAct = CreateAction(tr("&Connect"), tr("Connect"), tr("Connect objects at point."), QIcon(":/res/connect-tool.png"), QKeySequence("c,c"));
836         connect(connectAct, SIGNAL(triggered()), this, SLOT(HandleConnection()));
837
838         disconnectAct = CreateAction(tr("&Disconnect"), tr("Disconnect"), tr("Disconnect objects joined at point."), QIcon(":/res/disconnect-tool.png"), QKeySequence("d,d"));
839         connect(disconnectAct, SIGNAL(triggered()), this, SLOT(HandleDisconnection()));
840
841         mirrorAct = CreateAction(tr("&Mirror"), tr("Mirror"), tr("Mirror selected objects around a line."), QIcon(":/res/mirror-tool.png"), QKeySequence("m,i"), true);
842         connect(mirrorAct, SIGNAL(triggered()), this, SLOT(MirrorTool()));
843
844         trimAct = CreateAction(tr("&Trim"), tr("Trim"), tr("Trim extraneous lines from selected objects."), QIcon(":/res/trim-tool.png"), QKeySequence("t,r"), true);
845         connect(trimAct, SIGNAL(triggered()), this, SLOT(TrimTool()));
846
847         triangulateAct = CreateAction(tr("&Triangulate"), tr("Triangulate"), tr("Make triangles from selected lines, preserving their lengths."), QIcon(":/res/triangulate-tool.png"), QKeySequence("t,g"), true);
848         connect(triangulateAct, SIGNAL(triggered()), this, SLOT(TriangulateTool()));
849
850         editCutAct = CreateAction(tr("&Cut Objects"), tr("Cut Objects"), tr("Cut objects from the drawing to the clipboard."), QIcon(":/res/editcut2.png"), QKeySequence(tr("Ctrl+x")));
851         connect(editCutAct, SIGNAL(triggered()), this, SLOT(EditCut()));
852
853         editCopyAct = CreateAction(tr("&Copy Objects"), tr("Copy Objects"), tr("Copy objects from the drawing to the clipboard."), QIcon(":/res/editcopy2.png"), QKeySequence(tr("Ctrl+c")));
854         connect(editCopyAct, SIGNAL(triggered()), this, SLOT(EditCopy()));
855
856         editPasteAct = CreateAction(tr("&Paste Objects"), tr("Paste Objects"), tr("Paste objects from the clipboard to the drawing."), QIcon(":/res/editpaste2.png"), QKeySequence(tr("Ctrl+v")));
857         connect(editPasteAct, SIGNAL(triggered()), this, SLOT(EditPaste()));
858
859         selectAllAct = CreateAction(tr("Select &All"), tr("Select All Objects"), tr("Select all objects in the drawing."), QIcon(), QKeySequence(tr("Ctrl+a")));
860         connect(selectAllAct, SIGNAL(triggered()), this, SLOT(SelectAllObjects()));
861
862 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
863 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
864 /*      QActionGroup * group = new QActionGroup(this);
865         group->addAction(deleteAct);
866         group->addAction(addDimensionAct);
867         group->addAction(addLineAct);
868         group->addAction(addCircleAct);
869         group->addAction(addArcAct);//*/
870 }
871
872
873 //
874 // Consolidates action creation from a multi-step process to a single-step one.
875 //
876 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip,
877         QString statustip, QIcon icon, QKeySequence key, bool checkable/*= false*/)
878 {
879         QAction * action = new QAction(icon, name, this);
880         action->setToolTip(tooltip);
881         action->setStatusTip(statustip);
882         action->setShortcut(key);
883         action->setCheckable(checkable);
884
885         return action;
886 }
887
888
889 //
890 // This is essentially the same as the previous function, but this allows more
891 // than one key sequence to be added as key shortcuts.
892 //
893 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip,
894         QString statustip, QIcon icon, QKeySequence key1, QKeySequence key2,
895         bool checkable/*= false*/)
896 {
897         QAction * action = new QAction(icon, name, this);
898         action->setToolTip(tooltip);
899         action->setStatusTip(statustip);
900         QList<QKeySequence> keyList;
901         keyList.append(key1);
902         keyList.append(key2);
903         action->setShortcuts(keyList);
904         action->setCheckable(checkable);
905
906         return action;
907 }
908
909
910 void ApplicationWindow::CreateMenus(void)
911 {
912         QMenu * menu = menuBar()->addMenu(tr("&File"));
913         menu->addAction(fileNewAct);
914         menu->addAction(fileOpenAct);
915         menu->addAction(fileSaveAct);
916         menu->addAction(fileSaveAsAct);
917         menu->addAction(fileCloseAct);
918         menu->addSeparator();
919         menu->addAction(exitAct);
920
921         menu = menuBar()->addMenu(tr("&View"));
922         menu->addAction(zoomInAct);
923         menu->addAction(zoomOutAct);
924
925         menu = menuBar()->addMenu(tr("&Edit"));
926         menu->addAction(snapToGridAct);
927         menu->addAction(groupAct);
928         menu->addAction(fixAngleAct);
929         menu->addAction(fixLengthAct);
930         menu->addAction(rotateAct);
931         menu->addAction(mirrorAct);
932         menu->addAction(trimAct);
933         menu->addAction(triangulateAct);
934         menu->addAction(connectAct);
935         menu->addAction(disconnectAct);
936         menu->addSeparator();
937         menu->addAction(selectAllAct);
938         menu->addAction(editCutAct);
939         menu->addAction(editCopyAct);
940         menu->addAction(editPasteAct);
941         menu->addAction(deleteAct);
942         menu->addSeparator();
943         menu->addAction(addLineAct);
944         menu->addAction(addCircleAct);
945         menu->addAction(addArcAct);
946         menu->addAction(addPolygonAct);
947         menu->addAction(addSplineAct);
948         menu->addAction(addDimensionAct);
949         menu->addSeparator();
950         menu->addAction(settingsAct);
951
952         menu = menuBar()->addMenu(tr("&Help"));
953         menu->addAction(aboutAct);
954 }
955
956
957 void ApplicationWindow::CreateToolbars(void)
958 {
959         QToolBar * toolbar = addToolBar(tr("File"));
960         toolbar->setObjectName("File"); // Needed for saveState()
961         toolbar->addAction(fileNewAct);
962         toolbar->addAction(fileOpenAct);
963         toolbar->addAction(fileSaveAct);
964         toolbar->addAction(fileSaveAsAct);
965         toolbar->addAction(fileCloseAct);
966 //      toolbar->addAction(exitAct);
967
968         toolbar = addToolBar(tr("View"));
969         toolbar->setObjectName("View");
970         toolbar->addAction(zoomInAct);
971         toolbar->addAction(zoomOutAct);
972
973         QSpinBox * spinbox = new QSpinBox;
974         toolbar->addWidget(spinbox);
975 //      QLineEdit * lineedit = new QLineEdit;
976         toolbar->addWidget(baseUnitInput);
977         toolbar->addWidget(dimensionSizeInput);
978
979         toolbar = addToolBar(tr("Edit"));
980         toolbar->setObjectName("Edit");
981         toolbar->addAction(snapToGridAct);
982         toolbar->addAction(groupAct);
983         toolbar->addAction(fixAngleAct);
984         toolbar->addAction(fixLengthAct);
985         toolbar->addAction(rotateAct);
986         toolbar->addAction(mirrorAct);
987         toolbar->addAction(trimAct);
988         toolbar->addAction(triangulateAct);
989         toolbar->addAction(editCutAct);
990         toolbar->addAction(editCopyAct);
991         toolbar->addAction(editPasteAct);
992         toolbar->addAction(deleteAct);
993         toolbar->addAction(connectAct);
994         toolbar->addAction(disconnectAct);
995         toolbar->addSeparator();
996         toolbar->addAction(addLineAct);
997         toolbar->addAction(addCircleAct);
998         toolbar->addAction(addArcAct);
999         toolbar->addAction(addPolygonAct);
1000         toolbar->addAction(addSplineAct);
1001         toolbar->addAction(addDimensionAct);
1002
1003         spinbox->setRange(4, 256);
1004         spinbox->setValue(12);
1005         baseUnitInput->setText("12");
1006         connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(HandleGridSizeInPixels(int)));
1007         connect(baseUnitInput, SIGNAL(textChanged(QString)), this, SLOT(HandleGridSizeInBaseUnits(QString)));
1008         connect(dimensionSizeInput, SIGNAL(textChanged(QString)), this, SLOT(HandleDimensionSize(QString)));
1009
1010         PenWidget * pw = new PenWidget();
1011         toolbar = addToolBar(tr("Pen"));
1012         toolbar->setObjectName(tr("Pen"));
1013         toolbar->addWidget(pw);
1014         connect(drawing, SIGNAL(ObjectSelected(Object *)), pw, SLOT(SetFields(Object *)));
1015         connect(pw, SIGNAL(WidthSelected(float)), drawing, SLOT(HandlePenWidth(float)));
1016         connect(pw, SIGNAL(StyleSelected(int)), drawing, SLOT(HandlePenStyle(int)));
1017         connect(pw, SIGNAL(ColorSelected(uint32_t)), drawing, SLOT(HandlePenColor(uint32_t)));
1018         connect(pw, SIGNAL(StampSelected(void)), drawing, SLOT(HandlePenStamp(void)));
1019 }
1020
1021
1022 void ApplicationWindow::ReadSettings(void)
1023 {
1024         QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
1025         QSize size = settings.value("size", QSize(400, 400)).toSize();
1026         drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
1027         snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
1028         resize(size);
1029         move(pos);
1030         restoreState(settings.value("windowState").toByteArray());
1031 }
1032
1033
1034 void ApplicationWindow::WriteSettings(void)
1035 {
1036         settings.setValue("pos", pos());
1037         settings.setValue("size", size());
1038         settings.setValue("windowState", saveState());
1039         settings.setValue("useAntialiasing", drawing->useAntialiasing);
1040         settings.setValue("snapToGrid", snapToGridAct->isChecked());
1041 }
1042