2 // applicationwindow.cpp: Architektonas
4 // Part of the Architektonas Project
5 // (C) 2011 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
8 // JLH = James Hammons <jlhamm@acm.org>
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
24 // Uncomment this for debugging...
26 //#define DEBUGFOO // Various tool debugging...
27 //#define DEBUGTP // Toolpalette debugging...
29 #include "applicationwindow.h"
32 #include "blockwidget.h"
33 #include "drawingview.h"
35 #include "generaltab.h"
36 #include "layerwidget.h"
38 #include "settingsdialog.h"
41 ApplicationWindow::ApplicationWindow(): settings("Underground Software", "Architektonas")
43 drawing = new DrawingView(this);
44 drawing->setMouseTracking(true); // We want *all* mouse events...!
45 setCentralWidget(drawing);
47 aboutWin = new AboutWindow(this);
49 // ((TTEdit *)qApp)->charWnd = new CharWindow(this);
51 setWindowIcon(QIcon(":/res/atns-icon.png"));
52 setWindowTitle("Architektonas");
58 // Create Dock widgets
59 QDockWidget * dock1 = new QDockWidget(tr("Layers"), this);
60 LayerWidget * lw = new LayerWidget;
62 addDockWidget(Qt::RightDockWidgetArea, dock1);
63 QDockWidget * dock2 = new QDockWidget(tr("Blocks"), this);
64 BlockWidget * bw = new BlockWidget;
66 addDockWidget(Qt::RightDockWidgetArea, dock2);
67 // Needed for saveState()
68 dock1->setObjectName("Layers");
69 dock2->setObjectName("Blocks");
72 zoomIndicator = new QLabel("Grid: 12.0\" Zoom: 12.5%");
73 statusBar()->addPermanentWidget(zoomIndicator);
74 statusBar()->showMessage(tr("Ready"));
77 setUnifiedTitleAndToolBarOnMac(true);
78 Object::SetFont(new QFont("Verdana", 15, QFont::Bold));
82 void ApplicationWindow::closeEvent(QCloseEvent * event)
85 event->accept(); // Use ignore() if can't close for some reason
86 //Do we have a memory leak here if we don't delete the font in the Object???
90 void ApplicationWindow::FileNew(void)
92 // Should warn the user if drawing hasn't been saved...
93 drawing->document.Clear();
96 setWindowTitle("Architektonas - Untitled");
97 statusBar()->showMessage(tr("New drawing is ready."));
101 void ApplicationWindow::FileOpen(void)
103 QString filename = QFileDialog::getOpenFileName(this, tr("Open Drawing"),
104 "", tr("Architektonas files (*.drawing)"));
105 FILE * file = fopen(filename.toAscii().data(), "r");
110 msg.setText(QString(tr("Could not open file \"%1\" for loading!")).arg(filename));
111 msg.setIcon(QMessageBox::Critical);
116 Container container(Vector(0, 0));
117 bool successful = FileIO::LoadAtnsFile(file, &container);
123 msg.setText(QString(tr("Could not load file \"%1\"!")).arg(filename));
124 msg.setIcon(QMessageBox::Critical);
129 printf("FileOpen: container size = %li\n", container.objects.size());
130 drawing->document = container;
132 documentName = filename;
133 setWindowTitle(QString("Architektonas - %1").arg(documentName));
134 statusBar()->showMessage(tr("Drawing loaded."));
138 void ApplicationWindow::FileSave(void)
140 if (documentName.isEmpty())
141 documentName = QFileDialog::getSaveFileName(this, tr("Save Drawing"),
142 "", tr("Architektonas drawings (*.drawing)"));
144 FILE * file = fopen(documentName.toAscii().data(), "w");
149 msg.setText(QString(tr("Could not open file \"%1\" for saving!")).arg(documentName));
150 msg.setIcon(QMessageBox::Critical);
155 bool successful = FileIO::SaveAtnsFile(file, &drawing->document);
161 msg.setText(QString(tr("Could not save file \"%1\"!")).arg(documentName));
162 msg.setIcon(QMessageBox::Critical);
164 // In this case, we should unlink the created file, since it's not right...
165 unlink(documentName.toAscii().data());
169 setWindowTitle(QString("Architektonas - %1").arg(documentName));
170 statusBar()->showMessage(tr("Drawing saved."));
174 void ApplicationWindow::FileSaveAs(void)
176 QString filename = QFileDialog::getSaveFileName(this, tr("Save Drawing As"),
177 documentName, tr("Architektonas drawings (*.drawing)"));
179 if (!filename.isEmpty())
181 documentName = filename;
187 void ApplicationWindow::SnapToGridTool(void)
189 Object::SetSnapMode(snapToGridAct->isChecked());
193 void ApplicationWindow::FixAngle(void)
195 Object::SetFixedAngle(fixAngleAct->isChecked());
199 void ApplicationWindow::FixLength(void)
201 Object::SetFixedLength(fixLengthAct->isChecked());
205 // We want certain tools to be exclusive, and this approach isn't working correctly...
206 void ApplicationWindow::DeleteTool(void)
208 // For this tool, we check first to see if anything is selected. If so, we
209 // delete those and *don't* select the delete tool.
210 if (drawing->document.ItemsSelected() > 0)
212 drawing->document.DeleteSelectedItems();
214 deleteAct->setChecked(false);
218 // Otherwise, toggle the state of the tool
219 ClearUIToolStatesExcept(deleteAct);
220 SetInternalToolStates();
224 void ApplicationWindow::DimensionTool(void)
226 ClearUIToolStatesExcept(addDimensionAct);
227 SetInternalToolStates();
231 void ApplicationWindow::RotateTool(void)
233 ClearUIToolStatesExcept(rotateAct);
234 SetInternalToolStates();
238 void ApplicationWindow::AddLineTool(void)
240 ClearUIToolStatesExcept(addLineAct);
241 SetInternalToolStates();
245 void ApplicationWindow::AddCircleTool(void)
247 ClearUIToolStatesExcept(addCircleAct);
248 SetInternalToolStates();
252 void ApplicationWindow::AddArcTool(void)
254 ClearUIToolStatesExcept(addArcAct);
255 SetInternalToolStates();
259 void ApplicationWindow::AddPolygonTool(void)
261 ClearUIToolStatesExcept(addPolygonAct);
262 SetInternalToolStates();
266 void ApplicationWindow::ZoomInTool(void)
268 double zoomFactor = 2.0;
270 We need to find the center of the screen, then figure out where the new corner
271 will be in the zoomed in window.
273 So we know in Qt coords, the center is found via:
274 size.width() / 2 --> xCenter
275 size.height() / 2 --> yCenter
277 transform x/yCenter to Cartesian coordinates. So far, so good.
279 when zooming in, new origin will be (xCenter - origin.x) / 2, (yCenter - origin.y) / 2
280 (after subtracting from center, that is...)
282 QSize size = drawing->size();
283 Vector center(size.width() / 2.0, size.height() / 2.0);
284 //printf("Zoom in... Center=%.2f,%.2f; ", center.x, center.y);
285 center = Painter::QtToCartesianCoords(center);
286 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
287 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
288 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
289 Painter::origin = newOrigin;
291 //printf("Zoom in... level going from %02f to ", Painter::zoom);
292 // This just zooms leaving origin intact... should zoom in at the current center! [DONE]
293 // This should actually be calculated by drawing->gridPixels / grid size.
294 Painter::zoom *= zoomFactor;
295 drawing->gridSpacing = 12.0 / Painter::zoom;
296 // zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
297 zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(drawing->gridSpacing));
298 drawing->UpdateGridBackground();
303 void ApplicationWindow::ZoomOutTool(void)
308 origin = (223, 160.5)
309 newOrigin should be (-10, -10)
312 center - origin = (213, 150.5)
313 origin - center = (-213, -150.5)
315 + center = (-10, -10)
318 double zoomFactor = 2.0;
319 QSize size = drawing->size();
320 Vector center(size.width() / 2.0, size.height() / 2.0);
321 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
322 center = Painter::QtToCartesianCoords(center);
323 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
324 // Vector newOrigin = (center - Painter::origin) * zoomFactor;
325 // Vector newOrigin = center - (Painter::origin * zoomFactor);
326 Vector newOrigin = center + ((Painter::origin - center) * zoomFactor);
327 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
328 Painter::origin = newOrigin;
329 //printf("Zoom out...\n");
330 // This just zooms leaving origin intact... should zoom out at the current center! [DONE]
331 Painter::zoom /= zoomFactor;
332 drawing->gridSpacing = 12.0 / Painter::zoom;
333 // zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
334 zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(drawing->gridSpacing));
335 drawing->UpdateGridBackground();
340 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
342 if (exception != addArcAct)
343 addArcAct->setChecked(false);
345 if (exception != addCircleAct)
346 addCircleAct->setChecked(false);
348 if (exception != addDimensionAct)
349 addDimensionAct->setChecked(false);
351 if (exception != addLineAct)
352 addLineAct->setChecked(false);
354 if (exception != addPolygonAct)
355 addPolygonAct->setChecked(false);
357 if (exception != deleteAct)
358 deleteAct->setChecked(false);
360 if (exception != rotateAct)
361 rotateAct->setChecked(false);
365 void ApplicationWindow::SetInternalToolStates(void)
367 Object::SetDeleteActive(deleteAct->isChecked());
368 Object::SetDimensionActive(addDimensionAct->isChecked());
369 drawing->SetRotateToolActive(rotateAct->isChecked());
371 // We can be sure that if we've come here, then either an active tool is
372 // being deactivated, or a new tool is being created. In either case, the
373 // old tool needs to be deleted.
374 if (drawing->toolAction)
376 delete drawing->toolAction;
377 drawing->toolAction = NULL;
380 drawing->SetAddLineToolActive(addLineAct->isChecked());
381 drawing->SetAddCircleToolActive(addCircleAct->isChecked());
382 drawing->SetAddDimensionToolActive(addDimensionAct->isChecked());
386 void ApplicationWindow::HelpAbout(void)
392 void ApplicationWindow::Settings(void)
394 SettingsDialog dlg(this);
395 dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
397 if (dlg.exec() == false)
400 // Deal with stuff here (since user hit "OK" button...)
401 drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
407 // Group a bunch of selected objects (which can include other groups) together
408 // or ungroup a selected group.
410 void ApplicationWindow::HandleGrouping(void)
412 int itemsSelected = drawing->document.ItemsSelected();
414 // If nothing selected, do nothing
415 if (itemsSelected == 0)
417 statusBar()->showMessage(tr("No objects selected to make a group from."));
421 // If it's a group that's selected, ungroup it and leave the objects in a
423 if (itemsSelected == 1)
425 Object * object = drawing->document.SelectedItem(0);
429 printf("SelectedItem = NULL!\n");
431 printf("SelectedItem = %08X, type = %i\n", object, object->type);
434 if (object == NULL || object->type != OTContainer)
436 statusBar()->showMessage(tr("A group requires two or more selected objects."));
440 // Need the parent of the group, we're assuming here that the parent is
441 // the drawing's document. Does it matter? Maybe...
442 // Could just stipulate that grouping like this only takes place where the
443 // parent of the group is the drawing's document. Makes life much simpler.
444 ((Container *)object)->SelectAll();
445 ((Container *)object)->MoveContentsTo(&(drawing->document));
446 drawing->document.Delete(object);
447 statusBar()->showMessage(tr("Objects ungrouped."));
449 // Otherwise, if it's a group of 2 or more objects (which can be groups too)
450 // group them and select the group
451 else if (itemsSelected > 1)
453 Container * container = new Container(Vector(), &(drawing->document));
454 drawing->document.MoveSelectedContentsTo(container);
455 drawing->document.Add(container);
456 container->DeselectAll();
457 container->state = OSSelected;
458 statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(itemsSelected));
465 void ApplicationWindow::HandleGridSizeInPixels(int size)
467 drawing->SetGridSize(size);
472 void ApplicationWindow::CreateActions(void)
474 exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
475 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
476 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
478 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);
479 connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
481 fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
482 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
483 connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
485 fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
486 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
487 connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
489 deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(tr("Delete")), true);
490 connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
492 addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
493 connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
495 addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
496 connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
498 addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
499 connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
501 addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
502 connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
504 addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
505 connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
507 aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
508 connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
510 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);
511 connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
513 zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
514 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
516 zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
517 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
519 fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+n")));
520 connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
522 fileOpenAct = CreateAction(tr("&Open Drawing"), tr("Open Drawing"), tr("Opens an existing drawing from a file."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+o")));
523 connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
525 fileSaveAct = CreateAction(tr("&Save Drawing"), tr("Save Drawing"), tr("Saves the current drawing to a file."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+s")));
526 connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
528 fileSaveAsAct = CreateAction(tr("Save Drawing &As"), tr("Save As"), tr("Saves the current drawing to a file with a different name."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+Shift+s")));
529 connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
531 fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+w")));
533 settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/generic-tool.png"), QKeySequence());
534 connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
536 groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g"));
537 connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
539 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
540 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
541 /* QActionGroup * group = new QActionGroup(this);
542 group->addAction(deleteAct);
543 group->addAction(addDimensionAct);
544 group->addAction(addLineAct);
545 group->addAction(addCircleAct);
546 group->addAction(addArcAct);//*/
551 // Consolidates action creation from a multi-step process to a single-step one.
553 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
554 QIcon icon, QKeySequence key, bool checkable/*= false*/)
556 QAction * action = new QAction(icon, name, this);
557 action->setToolTip(tooltip);
558 action->setStatusTip(statustip);
559 action->setShortcut(key);
560 action->setCheckable(checkable);
567 // This is essentially the same as the previous function, but this allows more
568 // than one key sequence to be added as key shortcuts.
570 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
571 QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
573 QAction * action = new QAction(icon, name, this);
574 action->setToolTip(tooltip);
575 action->setStatusTip(statustip);
576 QList<QKeySequence> keyList;
577 keyList.append(key1);
578 keyList.append(key2);
579 action->setShortcuts(keyList);
580 action->setCheckable(checkable);
586 void ApplicationWindow::CreateMenus(void)
588 QMenu * menu = menuBar()->addMenu(tr("&File"));
589 menu->addAction(fileNewAct);
590 menu->addAction(fileOpenAct);
591 menu->addAction(fileSaveAct);
592 menu->addAction(fileSaveAsAct);
593 menu->addAction(fileCloseAct);
594 menu->addSeparator();
595 menu->addAction(exitAct);
597 menu = menuBar()->addMenu(tr("&View"));
598 menu->addAction(zoomInAct);
599 menu->addAction(zoomOutAct);
601 menu = menuBar()->addMenu(tr("&Edit"));
602 menu->addAction(snapToGridAct);
603 menu->addAction(groupAct);
604 menu->addAction(fixAngleAct);
605 menu->addAction(fixLengthAct);
606 menu->addAction(rotateAct);
607 menu->addSeparator();
608 menu->addAction(deleteAct);
609 menu->addSeparator();
610 menu->addAction(addLineAct);
611 menu->addAction(addCircleAct);
612 menu->addAction(addArcAct);
613 menu->addAction(addPolygonAct);
614 menu->addAction(addDimensionAct);
615 menu->addSeparator();
616 menu->addAction(settingsAct);
618 menu = menuBar()->addMenu(tr("&Help"));
619 menu->addAction(aboutAct);
623 void ApplicationWindow::CreateToolbars(void)
625 QToolBar * toolbar = addToolBar(tr("File"));
626 toolbar->setObjectName("File"); // Needed for saveState()
627 toolbar->addAction(exitAct);
629 toolbar = addToolBar(tr("View"));
630 toolbar->setObjectName("View");
631 toolbar->addAction(zoomInAct);
632 toolbar->addAction(zoomOutAct);
633 QSpinBox * spinbox = new QSpinBox;
634 toolbar->addWidget(spinbox);
636 toolbar = addToolBar(tr("Edit"));
637 toolbar->setObjectName("Edit");
638 toolbar->addAction(snapToGridAct);
639 toolbar->addAction(groupAct);
640 toolbar->addAction(fixAngleAct);
641 toolbar->addAction(fixLengthAct);
642 toolbar->addAction(rotateAct);
643 toolbar->addAction(deleteAct);
644 toolbar->addSeparator();
645 toolbar->addAction(addLineAct);
646 toolbar->addAction(addCircleAct);
647 toolbar->addAction(addArcAct);
648 toolbar->addAction(addPolygonAct);
649 toolbar->addAction(addDimensionAct);
651 spinbox->setRange(4, 256);
652 spinbox->setValue(12);
653 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(HandleGridSizeInPixels(int)));
657 void ApplicationWindow::ReadSettings(void)
659 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
660 QSize size = settings.value("size", QSize(400, 400)).toSize();
661 drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
662 snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
665 restoreState(settings.value("windowState").toByteArray());
669 void ApplicationWindow::WriteSettings(void)
671 settings.setValue("pos", pos());
672 settings.setValue("size", size());
673 settings.setValue("windowState", saveState());
674 settings.setValue("useAntialiasing", drawing->useAntialiasing);
675 settings.setValue("snapToGrid", snapToGridAct->isChecked());