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