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