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