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