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;
65 // dock2->setWidget(bw);
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 drawing->UpdateGridBackground();
301 void ApplicationWindow::ZoomOutTool(void)
306 origin = (223, 160.5)
307 newOrigin should be (-10, -10)
310 center - origin = (213, 150.5)
311 origin - center = (-213, -150.5)
313 + center = (-10, -10)
316 double zoomFactor = 2.0;
317 QSize size = drawing->size();
318 Vector center(size.width() / 2.0, size.height() / 2.0);
319 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
320 center = Painter::QtToCartesianCoords(center);
321 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
322 // Vector newOrigin = (center - Painter::origin) * zoomFactor;
323 // Vector newOrigin = center - (Painter::origin * zoomFactor);
324 Vector newOrigin = center + ((Painter::origin - center) * zoomFactor);
325 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
326 Painter::origin = newOrigin;
327 //printf("Zoom out...\n");
328 // This just zooms leaving origin intact... should zoom out at the current center! [DONE]
329 Painter::zoom /= zoomFactor;
330 drawing->gridSpacing = 12.0 / Painter::zoom;
331 zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
332 drawing->UpdateGridBackground();
337 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
339 if (exception != addArcAct)
340 addArcAct->setChecked(false);
342 if (exception != addCircleAct)
343 addCircleAct->setChecked(false);
345 if (exception != addDimensionAct)
346 addDimensionAct->setChecked(false);
348 if (exception != addLineAct)
349 addLineAct->setChecked(false);
351 if (exception != addPolygonAct)
352 addPolygonAct->setChecked(false);
354 if (exception != deleteAct)
355 deleteAct->setChecked(false);
357 if (exception != rotateAct)
358 rotateAct->setChecked(false);
362 void ApplicationWindow::SetInternalToolStates(void)
364 Object::SetDeleteActive(deleteAct->isChecked());
365 Object::SetDimensionActive(addDimensionAct->isChecked());
366 drawing->SetRotateToolActive(rotateAct->isChecked());
368 // We can be sure that if we've come here, then either an active tool is
369 // being deactivated, or a new tool is being created. In either case, the
370 // old tool needs to be deleted.
371 if (drawing->toolAction)
373 delete drawing->toolAction;
374 drawing->toolAction = NULL;
377 drawing->SetAddLineToolActive(addLineAct->isChecked());
378 drawing->SetAddCircleToolActive(addCircleAct->isChecked());
379 drawing->SetAddDimensionToolActive(addDimensionAct->isChecked());
383 void ApplicationWindow::HelpAbout(void)
389 void ApplicationWindow::Settings(void)
391 SettingsDialog dlg(this);
392 dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
394 if (dlg.exec() == false)
397 // Deal with stuff here (since user hit "OK" button...)
398 drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
404 // Group a bunch of selected objects (which can include other groups) together
405 // or ungroup a selected group.
407 void ApplicationWindow::HandleGrouping(void)
409 int itemsSelected = drawing->document.ItemsSelected();
411 // If nothing selected, do nothing
412 if (itemsSelected == 0)
414 statusBar()->showMessage(tr("No objects selected to make a group from."));
418 // If it's a group that's selected, ungroup it and leave the objects in a
420 if (itemsSelected == 1)
422 Object * object = drawing->document.SelectedItem(0);
426 printf("SelectedItem = NULL!\n");
428 printf("SelectedItem = %08X, type = %i\n", object, object->type);
431 if (object == NULL || object->type != OTContainer)
433 statusBar()->showMessage(tr("A group requires two or more selected objects."));
437 // Need the parent of the group, we're assuming here that the parent is
438 // the drawing's document. Does it matter? Maybe...
439 // Could just stipulate that grouping like this only takes place where the
440 // parent of the group is the drawing's document. Makes life much simpler.
441 ((Container *)object)->SelectAll();
442 ((Container *)object)->MoveContentsTo(&(drawing->document));
443 drawing->document.Delete(object);
444 statusBar()->showMessage(tr("Objects ungrouped."));
446 // Otherwise, if it's a group of 2 or more objects (which can be groups too)
447 // group them and select the group
448 else if (itemsSelected > 1)
450 Container * container = new Container(Vector(), &(drawing->document));
451 drawing->document.MoveSelectedContentsTo(container);
452 drawing->document.Add(container);
453 container->DeselectAll();
454 container->state = OSSelected;
455 statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(itemsSelected));
462 void ApplicationWindow::CreateActions(void)
464 exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
465 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
466 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
468 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);
469 connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
471 fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
472 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
473 connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
475 fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
476 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
477 connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
479 deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(tr("Delete")), true);
480 connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
482 addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
483 connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
485 addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
486 connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
488 addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
489 connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
491 addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
492 connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
494 addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
495 connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
497 aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
498 connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
500 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);
501 connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
503 zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
504 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
506 zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
507 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
509 fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+n")));
510 connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
512 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")));
513 connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
515 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")));
516 connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
518 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")));
519 connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
521 fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+w")));
523 settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/generic-tool.png"), QKeySequence());
524 connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
526 groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g"));
527 connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
529 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
530 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
531 /* QActionGroup * group = new QActionGroup(this);
532 group->addAction(deleteAct);
533 group->addAction(addDimensionAct);
534 group->addAction(addLineAct);
535 group->addAction(addCircleAct);
536 group->addAction(addArcAct);//*/
541 // Consolidates action creation from a multi-step process to a single-step one.
543 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
544 QIcon icon, QKeySequence key, bool checkable/*= false*/)
546 QAction * action = new QAction(icon, name, this);
547 action->setToolTip(tooltip);
548 action->setStatusTip(statustip);
549 action->setShortcut(key);
550 action->setCheckable(checkable);
557 // This is essentially the same as the previous function, but this allows more
558 // than one key sequence to be added as key shortcuts.
560 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
561 QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
563 QAction * action = new QAction(icon, name, this);
564 action->setToolTip(tooltip);
565 action->setStatusTip(statustip);
566 QList<QKeySequence> keyList;
567 keyList.append(key1);
568 keyList.append(key2);
569 action->setShortcuts(keyList);
570 action->setCheckable(checkable);
576 void ApplicationWindow::CreateMenus(void)
578 QMenu * menu = menuBar()->addMenu(tr("&File"));
579 menu->addAction(fileNewAct);
580 menu->addAction(fileOpenAct);
581 menu->addAction(fileSaveAct);
582 menu->addAction(fileSaveAsAct);
583 menu->addAction(fileCloseAct);
584 menu->addSeparator();
585 menu->addAction(exitAct);
587 menu = menuBar()->addMenu(tr("&View"));
588 menu->addAction(zoomInAct);
589 menu->addAction(zoomOutAct);
591 menu = menuBar()->addMenu(tr("&Edit"));
592 menu->addAction(snapToGridAct);
593 menu->addAction(groupAct);
594 menu->addAction(fixAngleAct);
595 menu->addAction(fixLengthAct);
596 menu->addAction(rotateAct);
597 menu->addSeparator();
598 menu->addAction(deleteAct);
599 menu->addSeparator();
600 menu->addAction(addLineAct);
601 menu->addAction(addCircleAct);
602 menu->addAction(addArcAct);
603 menu->addAction(addPolygonAct);
604 menu->addAction(addDimensionAct);
605 menu->addSeparator();
606 menu->addAction(settingsAct);
608 menu = menuBar()->addMenu(tr("&Help"));
609 menu->addAction(aboutAct);
613 void ApplicationWindow::CreateToolbars(void)
615 QToolBar * toolbar = addToolBar(tr("File"));
616 toolbar->setObjectName("File"); // Needed for saveState()
617 toolbar->addAction(exitAct);
619 toolbar = addToolBar(tr("View"));
620 toolbar->setObjectName("View");
621 toolbar->addAction(zoomInAct);
622 toolbar->addAction(zoomOutAct);
624 toolbar = addToolBar(tr("Edit"));
625 toolbar->setObjectName("Edit");
626 toolbar->addAction(snapToGridAct);
627 toolbar->addAction(groupAct);
628 toolbar->addAction(fixAngleAct);
629 toolbar->addAction(fixLengthAct);
630 toolbar->addAction(rotateAct);
631 toolbar->addAction(deleteAct);
632 toolbar->addSeparator();
633 toolbar->addAction(addLineAct);
634 toolbar->addAction(addCircleAct);
635 toolbar->addAction(addArcAct);
636 toolbar->addAction(addPolygonAct);
637 toolbar->addAction(addDimensionAct);
641 void ApplicationWindow::ReadSettings(void)
643 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
644 QSize size = settings.value("size", QSize(400, 400)).toSize();
645 drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
646 snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
649 restoreState(settings.value("windowState").toByteArray());
650 // pos = settings.value("charWndPos", QPoint(0, 0)).toPoint();
651 // size = settings.value("charWndSize", QSize(200, 200)).toSize();
652 // ((TTEdit *)qApp)->charWnd->resize(size);
653 // ((TTEdit *)qApp)->charWnd->move(pos);
657 void ApplicationWindow::WriteSettings(void)
659 settings.setValue("pos", pos());
660 settings.setValue("size", size());
661 settings.setValue("windowState", saveState());
662 settings.setValue("useAntialiasing", drawing->useAntialiasing);
663 settings.setValue("snapToGrid", snapToGridAct->isChecked());
664 // settings.setValue("charWndPos", ((TTEdit *)qApp)->charWnd->pos());
665 // settings.setValue("charWndSize", ((TTEdit *)qApp)->charWnd->size());