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