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