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