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 "drawingview.h"
34 #include "generaltab.h"
36 #include "settingsdialog.h"
39 ApplicationWindow::ApplicationWindow(): settings("Underground Software", "Architektonas")
41 drawing = new DrawingView(this);
42 drawing->setMouseTracking(true); // We want *all* mouse events...!
43 setCentralWidget(drawing);
45 aboutWin = new AboutWindow(this);
47 // ((TTEdit *)qApp)->charWnd = new CharWindow(this);
49 setWindowIcon(QIcon(":/res/atns-icon.png"));
50 setWindowTitle("Architektonas");
57 zoomIndicator = new QLabel("Grid: 12.0\" Zoom: 12.5%");
58 statusBar()->addPermanentWidget(zoomIndicator);
59 statusBar()->showMessage(tr("Ready"));
63 // connect(textEdit->document(), SIGNAL(contentsChanged()),
64 // this, SLOT(documentWasModified()));
66 // setCurrentFile("");
67 setUnifiedTitleAndToolBarOnMac(true);
69 // ((TTEdit *)qApp)->charWnd->show();//eh?
70 Object::SetFont(new QFont("Verdana", 15, QFont::Bold));
74 void ApplicationWindow::closeEvent(QCloseEvent * event)
77 event->accept(); // Use ignore() if can't close for some reason
78 //Do we have a memory leak here if we don't delete the font in the Object???
82 void ApplicationWindow::FileNew(void)
84 // Should warn the user if drawing hasn't been saved...
85 drawing->document.Clear();
88 setWindowTitle("Architektonas - Untitled");
89 statusBar()->showMessage(tr("New drawing is ready."));
93 void ApplicationWindow::FileOpen(void)
95 QString filename = QFileDialog::getOpenFileName(this, tr("Open Drawing"),
96 "", tr("Architektonas files (*.drawing)"));
97 FILE * file = fopen(filename.toAscii().data(), "r");
102 msg.setText(QString(tr("Could not open file \"%1\" for loading!")).arg(filename));
103 msg.setIcon(QMessageBox::Critical);
108 Container container(Vector(0, 0));
109 bool successful = FileIO::LoadAtnsFile(file, &container);
115 msg.setText(QString(tr("Could not load file \"%1\"!")).arg(filename));
116 msg.setIcon(QMessageBox::Critical);
121 printf("FileOpen: container size = %li\n", container.objects.size());
122 drawing->document = container;
124 documentName = filename;
125 setWindowTitle(QString("Architektonas - %1").arg(documentName));
126 statusBar()->showMessage(tr("Drawing loaded."));
130 void ApplicationWindow::FileSave(void)
132 if (documentName.isEmpty())
133 documentName = QFileDialog::getSaveFileName(this, tr("Save Drawing"),
134 "", tr("Architektonas drawings (*.drawing)"));
136 FILE * file = fopen(documentName.toAscii().data(), "w");
141 msg.setText(QString(tr("Could not open file \"%1\" for saving!")).arg(documentName));
142 msg.setIcon(QMessageBox::Critical);
147 bool successful = FileIO::SaveAtnsFile(file, &drawing->document);
153 msg.setText(QString(tr("Could not save file \"%1\"!")).arg(documentName));
154 msg.setIcon(QMessageBox::Critical);
156 // In this case, we should unlink the created file, since it's not right...
157 unlink(documentName.toAscii().data());
161 setWindowTitle(QString("Architektonas - %1").arg(documentName));
162 statusBar()->showMessage(tr("Drawing saved."));
166 void ApplicationWindow::FileSaveAs(void)
168 QString filename = QFileDialog::getSaveFileName(this, tr("Save Drawing As"),
169 documentName, tr("Architektonas drawings (*.drawing)"));
171 if (!filename.isEmpty())
173 documentName = filename;
179 void ApplicationWindow::SnapToGridTool(void)
181 Object::SetSnapMode(snapToGridAct->isChecked());
185 void ApplicationWindow::FixAngle(void)
187 Object::SetFixedAngle(fixAngleAct->isChecked());
191 void ApplicationWindow::FixLength(void)
193 Object::SetFixedLength(fixLengthAct->isChecked());
197 // We want certain tools to be exclusive, and this approach isn't working correctly...
198 void ApplicationWindow::DeleteTool(void)
201 ClearUIToolStatesExcept(deleteAct);
202 SetInternalToolStates();
206 void ApplicationWindow::DimensionTool(void)
208 ClearUIToolStatesExcept(addDimensionAct);
209 SetInternalToolStates();
213 void ApplicationWindow::RotateTool(void)
215 ClearUIToolStatesExcept(rotateAct);
216 SetInternalToolStates();
220 void ApplicationWindow::AddLineTool(void)
222 ClearUIToolStatesExcept(addLineAct);
223 SetInternalToolStates();
227 void ApplicationWindow::AddCircleTool(void)
229 ClearUIToolStatesExcept(addCircleAct);
230 SetInternalToolStates();
234 void ApplicationWindow::AddArcTool(void)
236 ClearUIToolStatesExcept(addArcAct);
237 SetInternalToolStates();
241 void ApplicationWindow::AddPolygonTool(void)
243 ClearUIToolStatesExcept(addPolygonAct);
244 SetInternalToolStates();
248 void ApplicationWindow::ZoomInTool(void)
250 double zoomFactor = 2.0;
252 We need to find the center of the screen, then figure out where the new corner
253 will be in the zoomed in window.
255 So we know in Qt coords, the center is found via:
256 size.width() / 2 --> xCenter
257 size.height() / 2 --> yCenter
259 transform x/yCenter to Cartesian coordinates. So far, so good.
261 when zooming in, new origin will be (xCenter - origin.x) / 2, (yCenter - origin.y) / 2
262 (after subtracting from center, that is...)
264 QSize size = drawing->size();
265 Vector center(size.width() / 2.0, size.height() / 2.0);
266 //printf("Zoom in... Center=%.2f,%.2f; ", center.x, center.y);
267 center = Painter::QtToCartesianCoords(center);
268 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
269 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
270 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
271 Painter::origin = newOrigin;
273 //printf("Zoom in... level going from %02f to ", Painter::zoom);
274 // This just zooms leaving origin intact... should zoom in at the current center! [DONE]
275 Painter::zoom *= zoomFactor;
276 drawing->gridSpacing = 12.0 / Painter::zoom;
277 zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
278 drawing->UpdateGridBackground();
283 void ApplicationWindow::ZoomOutTool(void)
288 origin = (223, 160.5)
289 newOrigin should be (-10, -10)
292 center - origin = (213, 150.5)
293 origin - center = (-213, -150.5)
295 + center = (-10, -10)
298 double zoomFactor = 2.0;
299 QSize size = drawing->size();
300 Vector center(size.width() / 2.0, size.height() / 2.0);
301 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
302 center = Painter::QtToCartesianCoords(center);
303 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
304 // Vector newOrigin = (center - Painter::origin) * zoomFactor;
305 // Vector newOrigin = center - (Painter::origin * zoomFactor);
306 Vector newOrigin = center + ((Painter::origin - center) * zoomFactor);
307 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
308 Painter::origin = newOrigin;
309 //printf("Zoom out...\n");
310 // This just zooms leaving origin intact... should zoom out at the current center! [DONE]
311 Painter::zoom /= zoomFactor;
312 drawing->gridSpacing = 12.0 / Painter::zoom;
313 zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
314 drawing->UpdateGridBackground();
319 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
321 if (exception != addArcAct)
322 addArcAct->setChecked(false);
324 if (exception != addCircleAct)
325 addCircleAct->setChecked(false);
327 if (exception != addDimensionAct)
328 addDimensionAct->setChecked(false);
330 if (exception != addLineAct)
331 addLineAct->setChecked(false);
333 if (exception != addPolygonAct)
334 addPolygonAct->setChecked(false);
336 if (exception != deleteAct)
337 deleteAct->setChecked(false);
339 if (exception != rotateAct)
340 rotateAct->setChecked(false);
344 void ApplicationWindow::SetInternalToolStates(void)
346 Object::SetDeleteActive(deleteAct->isChecked());
347 Object::SetDimensionActive(addDimensionAct->isChecked());
348 drawing->SetRotateToolActive(rotateAct->isChecked());
350 // We can be sure that if we've come here, then either an active tool is
351 // being deactivated, or a new tool is being created. In either case, the
352 // old tool needs to be deleted.
353 if (drawing->toolAction)
355 delete drawing->toolAction;
356 drawing->toolAction = NULL;
359 drawing->SetAddLineToolActive(addLineAct->isChecked());
360 drawing->SetAddCircleToolActive(addCircleAct->isChecked());
361 drawing->SetAddDimensionToolActive(addDimensionAct->isChecked());
365 void ApplicationWindow::HelpAbout(void)
371 void ApplicationWindow::Settings(void)
373 SettingsDialog dlg(this);
374 dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
376 if (dlg.exec() == false)
379 // Deal with stuff here (since user hit "OK" button...)
380 drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
385 void ApplicationWindow::HandleGrouping(void)
387 // Group a bunch of selected objects together or ungroup a selected group.
389 if (drawing->document.ItemsSelected() == 0)
391 statusBar()->showMessage(tr("No objects selected to make a group from."));
395 // If it's a group that's selected, ungroup it and leave the objects in a
397 if (drawing->document.ItemsSelected() == 1)
399 Object * object = drawing->document.SelectedItem(0);
403 printf("SelectedItem = NULL!\n");
405 printf("SelectedItem = %08X, type = %i\n", object, object->type);
408 if (object == NULL || object->type != OTContainer)
410 statusBar()->showMessage(tr("A group requires two or more selected objects."));
414 // Need the parent of the group, we're assuming here that the parent is
415 // the drawing's document. Does it matter? Maybe...
416 // Could just stipulate that grouping like this only takes place where the
417 // parent of the group is the drawing's document. Makes life much simpler.
418 ((Container *)object)->SelectAll();
419 ((Container *)object)->MoveContentsTo(&(drawing->document));
420 drawing->document.Delete(object);
422 // Otherwise, if it's a group of 2 or more objects (which can be groups too)
423 // group them and select the group
424 else if (drawing->document.ItemsSelected() > 1)
426 Container * container = new Container(Vector(), &(drawing->document));
427 drawing->document.MoveSelectedContentsTo(container);
428 drawing->document.Add(container);
429 container->DeselectAll();
430 container->state = OSSelected;
437 void ApplicationWindow::CreateActions(void)
439 exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
440 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
441 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
443 snapToGridAct = CreateAction(tr("Snap To &Grid"), tr("Snap To Grid"), tr("Snaps mouse cursor to the visible grid when moving/creating objects."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("S")), true);
444 connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
446 fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
447 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
448 connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
450 fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
451 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
452 connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
454 deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(), true);
455 connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
457 addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
458 connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
460 addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
461 connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
463 addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
464 connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
466 addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
467 connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
469 addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
470 connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
472 aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
473 connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
475 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);
476 connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
478 zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
479 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
481 zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
482 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
484 fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+n")));
485 connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
487 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")));
488 connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
490 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")));
491 connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
493 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")));
494 connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
496 fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+w")));
498 settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/generic-tool.png"), QKeySequence());
499 connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
501 groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/generic-tool.png"), QKeySequence("g"));
502 connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
504 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
505 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
506 /* QActionGroup * group = new QActionGroup(this);
507 group->addAction(deleteAct);
508 group->addAction(addDimensionAct);
509 group->addAction(addLineAct);
510 group->addAction(addCircleAct);
511 group->addAction(addArcAct);//*/
516 // Consolidates action creation from a multi-step process to a single-step one.
518 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
519 QIcon icon, QKeySequence key, bool checkable/*= false*/)
521 QAction * action = new QAction(icon, name, this);
522 action->setToolTip(tooltip);
523 action->setStatusTip(statustip);
524 action->setShortcut(key);
525 action->setCheckable(checkable);
532 // This is essentially the same as the previous function, but this allows more
533 // than one key sequence to be added as key shortcuts.
535 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
536 QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
538 QAction * action = new QAction(icon, name, this);
539 action->setToolTip(tooltip);
540 action->setStatusTip(statustip);
541 QList<QKeySequence> keyList;
542 keyList.append(key1);
543 keyList.append(key2);
544 action->setShortcuts(keyList);
545 action->setCheckable(checkable);
551 void ApplicationWindow::CreateMenus(void)
553 QMenu * menu = menuBar()->addMenu(tr("&File"));
554 menu->addAction(fileNewAct);
555 menu->addAction(fileOpenAct);
556 menu->addAction(fileSaveAct);
557 menu->addAction(fileSaveAsAct);
558 menu->addAction(fileCloseAct);
559 menu->addSeparator();
560 menu->addAction(exitAct);
562 menu = menuBar()->addMenu(tr("&View"));
563 menu->addAction(zoomInAct);
564 menu->addAction(zoomOutAct);
566 menu = menuBar()->addMenu(tr("&Edit"));
567 menu->addAction(snapToGridAct);
568 menu->addAction(groupAct);
569 menu->addAction(fixAngleAct);
570 menu->addAction(fixLengthAct);
571 menu->addAction(rotateAct);
572 menu->addSeparator();
573 menu->addAction(deleteAct);
574 menu->addSeparator();
575 menu->addAction(addLineAct);
576 menu->addAction(addCircleAct);
577 menu->addAction(addArcAct);
578 menu->addAction(addPolygonAct);
579 menu->addAction(addDimensionAct);
580 menu->addSeparator();
581 menu->addAction(settingsAct);
583 menu = menuBar()->addMenu(tr("&Help"));
584 menu->addAction(aboutAct);
588 void ApplicationWindow::CreateToolbars(void)
590 QToolBar * toolbar = addToolBar(tr("File"));
591 toolbar->addAction(exitAct);
593 toolbar = addToolBar(tr("View"));
594 toolbar->addAction(zoomInAct);
595 toolbar->addAction(zoomOutAct);
597 toolbar = addToolBar(tr("Edit"));
598 toolbar->addAction(snapToGridAct);
599 toolbar->addAction(groupAct);
600 toolbar->addAction(fixAngleAct);
601 toolbar->addAction(fixLengthAct);
602 toolbar->addAction(rotateAct);
603 toolbar->addAction(deleteAct);
604 toolbar->addSeparator();
605 toolbar->addAction(addLineAct);
606 toolbar->addAction(addCircleAct);
607 toolbar->addAction(addArcAct);
608 toolbar->addAction(addPolygonAct);
609 toolbar->addAction(addDimensionAct);
613 void ApplicationWindow::ReadSettings(void)
615 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
616 QSize size = settings.value("size", QSize(400, 400)).toSize();
617 drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
618 snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
621 // pos = settings.value("charWndPos", QPoint(0, 0)).toPoint();
622 // size = settings.value("charWndSize", QSize(200, 200)).toSize();
623 // ((TTEdit *)qApp)->charWnd->resize(size);
624 // ((TTEdit *)qApp)->charWnd->move(pos);
628 void ApplicationWindow::WriteSettings(void)
630 settings.setValue("pos", pos());
631 settings.setValue("size", size());
632 settings.setValue("useAntialiasing", drawing->useAntialiasing);
633 settings.setValue("snapToGrid", snapToGridAct->isChecked());
634 // settings.setValue("charWndPos", ((TTEdit *)qApp)->charWnd->pos());
635 // settings.setValue("charWndSize", ((TTEdit *)qApp)->charWnd->size());