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