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 Painter::zoom *= zoomFactor;
294 drawing->gridSpacing = 12.0 / Painter::zoom;
295 // zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
296 zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(drawing->gridSpacing));
297 drawing->UpdateGridBackground();
302 void ApplicationWindow::ZoomOutTool(void)
307 origin = (223, 160.5)
308 newOrigin should be (-10, -10)
311 center - origin = (213, 150.5)
312 origin - center = (-213, -150.5)
314 + center = (-10, -10)
317 double zoomFactor = 2.0;
318 QSize size = drawing->size();
319 Vector center(size.width() / 2.0, size.height() / 2.0);
320 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
321 center = Painter::QtToCartesianCoords(center);
322 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
323 // Vector newOrigin = (center - Painter::origin) * zoomFactor;
324 // Vector newOrigin = center - (Painter::origin * zoomFactor);
325 Vector newOrigin = center + ((Painter::origin - center) * zoomFactor);
326 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
327 Painter::origin = newOrigin;
328 //printf("Zoom out...\n");
329 // This just zooms leaving origin intact... should zoom out at the current center! [DONE]
330 Painter::zoom /= zoomFactor;
331 drawing->gridSpacing = 12.0 / Painter::zoom;
332 // zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
333 zoomIndicator->setText(QString("Grid: %1\", BU: Inch").arg(drawing->gridSpacing));
334 drawing->UpdateGridBackground();
339 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
341 if (exception != addArcAct)
342 addArcAct->setChecked(false);
344 if (exception != addCircleAct)
345 addCircleAct->setChecked(false);
347 if (exception != addDimensionAct)
348 addDimensionAct->setChecked(false);
350 if (exception != addLineAct)
351 addLineAct->setChecked(false);
353 if (exception != addPolygonAct)
354 addPolygonAct->setChecked(false);
356 if (exception != deleteAct)
357 deleteAct->setChecked(false);
359 if (exception != rotateAct)
360 rotateAct->setChecked(false);
364 void ApplicationWindow::SetInternalToolStates(void)
366 Object::SetDeleteActive(deleteAct->isChecked());
367 Object::SetDimensionActive(addDimensionAct->isChecked());
368 drawing->SetRotateToolActive(rotateAct->isChecked());
370 // We can be sure that if we've come here, then either an active tool is
371 // being deactivated, or a new tool is being created. In either case, the
372 // old tool needs to be deleted.
373 if (drawing->toolAction)
375 delete drawing->toolAction;
376 drawing->toolAction = NULL;
379 drawing->SetAddLineToolActive(addLineAct->isChecked());
380 drawing->SetAddCircleToolActive(addCircleAct->isChecked());
381 drawing->SetAddDimensionToolActive(addDimensionAct->isChecked());
385 void ApplicationWindow::HelpAbout(void)
391 void ApplicationWindow::Settings(void)
393 SettingsDialog dlg(this);
394 dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
396 if (dlg.exec() == false)
399 // Deal with stuff here (since user hit "OK" button...)
400 drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
406 // Group a bunch of selected objects (which can include other groups) together
407 // or ungroup a selected group.
409 void ApplicationWindow::HandleGrouping(void)
411 int itemsSelected = drawing->document.ItemsSelected();
413 // If nothing selected, do nothing
414 if (itemsSelected == 0)
416 statusBar()->showMessage(tr("No objects selected to make a group from."));
420 // If it's a group that's selected, ungroup it and leave the objects in a
422 if (itemsSelected == 1)
424 Object * object = drawing->document.SelectedItem(0);
428 printf("SelectedItem = NULL!\n");
430 printf("SelectedItem = %08X, type = %i\n", object, object->type);
433 if (object == NULL || object->type != OTContainer)
435 statusBar()->showMessage(tr("A group requires two or more selected objects."));
439 // Need the parent of the group, we're assuming here that the parent is
440 // the drawing's document. Does it matter? Maybe...
441 // Could just stipulate that grouping like this only takes place where the
442 // parent of the group is the drawing's document. Makes life much simpler.
443 ((Container *)object)->SelectAll();
444 ((Container *)object)->MoveContentsTo(&(drawing->document));
445 drawing->document.Delete(object);
446 statusBar()->showMessage(tr("Objects ungrouped."));
448 // Otherwise, if it's a group of 2 or more objects (which can be groups too)
449 // group them and select the group
450 else if (itemsSelected > 1)
452 Container * container = new Container(Vector(), &(drawing->document));
453 drawing->document.MoveSelectedContentsTo(container);
454 drawing->document.Add(container);
455 container->DeselectAll();
456 container->state = OSSelected;
457 statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(itemsSelected));
464 void ApplicationWindow::CreateActions(void)
466 exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
467 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
468 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
470 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);
471 connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
473 fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
474 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
475 connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
477 fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
478 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
479 connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
481 deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(tr("Delete")), true);
482 connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
484 addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
485 connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
487 addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
488 connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
490 addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
491 connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
493 addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
494 connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
496 addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
497 connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
499 aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
500 connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
502 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);
503 connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
505 zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
506 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
508 zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
509 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
511 fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+n")));
512 connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
514 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")));
515 connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
517 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")));
518 connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
520 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")));
521 connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
523 fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+w")));
525 settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/generic-tool.png"), QKeySequence());
526 connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
528 groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g"));
529 connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
531 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
532 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
533 /* QActionGroup * group = new QActionGroup(this);
534 group->addAction(deleteAct);
535 group->addAction(addDimensionAct);
536 group->addAction(addLineAct);
537 group->addAction(addCircleAct);
538 group->addAction(addArcAct);//*/
543 // Consolidates action creation from a multi-step process to a single-step one.
545 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
546 QIcon icon, QKeySequence key, bool checkable/*= false*/)
548 QAction * action = new QAction(icon, name, this);
549 action->setToolTip(tooltip);
550 action->setStatusTip(statustip);
551 action->setShortcut(key);
552 action->setCheckable(checkable);
559 // This is essentially the same as the previous function, but this allows more
560 // than one key sequence to be added as key shortcuts.
562 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
563 QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
565 QAction * action = new QAction(icon, name, this);
566 action->setToolTip(tooltip);
567 action->setStatusTip(statustip);
568 QList<QKeySequence> keyList;
569 keyList.append(key1);
570 keyList.append(key2);
571 action->setShortcuts(keyList);
572 action->setCheckable(checkable);
578 void ApplicationWindow::CreateMenus(void)
580 QMenu * menu = menuBar()->addMenu(tr("&File"));
581 menu->addAction(fileNewAct);
582 menu->addAction(fileOpenAct);
583 menu->addAction(fileSaveAct);
584 menu->addAction(fileSaveAsAct);
585 menu->addAction(fileCloseAct);
586 menu->addSeparator();
587 menu->addAction(exitAct);
589 menu = menuBar()->addMenu(tr("&View"));
590 menu->addAction(zoomInAct);
591 menu->addAction(zoomOutAct);
593 menu = menuBar()->addMenu(tr("&Edit"));
594 menu->addAction(snapToGridAct);
595 menu->addAction(groupAct);
596 menu->addAction(fixAngleAct);
597 menu->addAction(fixLengthAct);
598 menu->addAction(rotateAct);
599 menu->addSeparator();
600 menu->addAction(deleteAct);
601 menu->addSeparator();
602 menu->addAction(addLineAct);
603 menu->addAction(addCircleAct);
604 menu->addAction(addArcAct);
605 menu->addAction(addPolygonAct);
606 menu->addAction(addDimensionAct);
607 menu->addSeparator();
608 menu->addAction(settingsAct);
610 menu = menuBar()->addMenu(tr("&Help"));
611 menu->addAction(aboutAct);
615 void ApplicationWindow::CreateToolbars(void)
617 QToolBar * toolbar = addToolBar(tr("File"));
618 toolbar->setObjectName("File"); // Needed for saveState()
619 toolbar->addAction(exitAct);
621 toolbar = addToolBar(tr("View"));
622 toolbar->setObjectName("View");
623 toolbar->addAction(zoomInAct);
624 toolbar->addAction(zoomOutAct);
626 toolbar = addToolBar(tr("Edit"));
627 toolbar->setObjectName("Edit");
628 toolbar->addAction(snapToGridAct);
629 toolbar->addAction(groupAct);
630 toolbar->addAction(fixAngleAct);
631 toolbar->addAction(fixLengthAct);
632 toolbar->addAction(rotateAct);
633 toolbar->addAction(deleteAct);
634 toolbar->addSeparator();
635 toolbar->addAction(addLineAct);
636 toolbar->addAction(addCircleAct);
637 toolbar->addAction(addArcAct);
638 toolbar->addAction(addPolygonAct);
639 toolbar->addAction(addDimensionAct);
643 void ApplicationWindow::ReadSettings(void)
645 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
646 QSize size = settings.value("size", QSize(400, 400)).toSize();
647 drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
648 snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
651 restoreState(settings.value("windowState").toByteArray());
652 // pos = settings.value("charWndPos", QPoint(0, 0)).toPoint();
653 // size = settings.value("charWndSize", QSize(200, 200)).toSize();
654 // ((TTEdit *)qApp)->charWnd->resize(size);
655 // ((TTEdit *)qApp)->charWnd->move(pos);
659 void ApplicationWindow::WriteSettings(void)
661 settings.setValue("pos", pos());
662 settings.setValue("size", size());
663 settings.setValue("windowState", saveState());
664 settings.setValue("useAntialiasing", drawing->useAntialiasing);
665 settings.setValue("snapToGrid", snapToGridAct->isChecked());
666 // settings.setValue("charWndPos", ((TTEdit *)qApp)->charWnd->pos());
667 // settings.setValue("charWndSize", ((TTEdit *)qApp)->charWnd->size());