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