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