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)
200 // For this tool, we check first to see if anything is selected. If so, we
201 // delete those and *don't* select the delete tool.
202 if (drawing->document.ItemsSelected() > 0)
204 drawing->document.DeleteSelectedItems();
206 deleteAct->setChecked(false);
210 // Otherwise, toggle the state of the tool
211 ClearUIToolStatesExcept(deleteAct);
212 SetInternalToolStates();
216 void ApplicationWindow::DimensionTool(void)
218 ClearUIToolStatesExcept(addDimensionAct);
219 SetInternalToolStates();
223 void ApplicationWindow::RotateTool(void)
225 ClearUIToolStatesExcept(rotateAct);
226 SetInternalToolStates();
230 void ApplicationWindow::AddLineTool(void)
232 ClearUIToolStatesExcept(addLineAct);
233 SetInternalToolStates();
237 void ApplicationWindow::AddCircleTool(void)
239 ClearUIToolStatesExcept(addCircleAct);
240 SetInternalToolStates();
244 void ApplicationWindow::AddArcTool(void)
246 ClearUIToolStatesExcept(addArcAct);
247 SetInternalToolStates();
251 void ApplicationWindow::AddPolygonTool(void)
253 ClearUIToolStatesExcept(addPolygonAct);
254 SetInternalToolStates();
258 void ApplicationWindow::ZoomInTool(void)
260 double zoomFactor = 2.0;
262 We need to find the center of the screen, then figure out where the new corner
263 will be in the zoomed in window.
265 So we know in Qt coords, the center is found via:
266 size.width() / 2 --> xCenter
267 size.height() / 2 --> yCenter
269 transform x/yCenter to Cartesian coordinates. So far, so good.
271 when zooming in, new origin will be (xCenter - origin.x) / 2, (yCenter - origin.y) / 2
272 (after subtracting from center, that is...)
274 QSize size = drawing->size();
275 Vector center(size.width() / 2.0, size.height() / 2.0);
276 //printf("Zoom in... Center=%.2f,%.2f; ", center.x, center.y);
277 center = Painter::QtToCartesianCoords(center);
278 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
279 Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
280 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
281 Painter::origin = newOrigin;
283 //printf("Zoom in... level going from %02f to ", Painter::zoom);
284 // This just zooms leaving origin intact... should zoom in at the current center! [DONE]
285 Painter::zoom *= zoomFactor;
286 drawing->gridSpacing = 12.0 / Painter::zoom;
287 zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
288 drawing->UpdateGridBackground();
293 void ApplicationWindow::ZoomOutTool(void)
298 origin = (223, 160.5)
299 newOrigin should be (-10, -10)
302 center - origin = (213, 150.5)
303 origin - center = (-213, -150.5)
305 + center = (-10, -10)
308 double zoomFactor = 2.0;
309 QSize size = drawing->size();
310 Vector center(size.width() / 2.0, size.height() / 2.0);
311 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
312 center = Painter::QtToCartesianCoords(center);
313 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
314 // Vector newOrigin = (center - Painter::origin) * zoomFactor;
315 // Vector newOrigin = center - (Painter::origin * zoomFactor);
316 Vector newOrigin = center + ((Painter::origin - center) * zoomFactor);
317 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
318 Painter::origin = newOrigin;
319 //printf("Zoom out...\n");
320 // This just zooms leaving origin intact... should zoom out at the current center! [DONE]
321 Painter::zoom /= zoomFactor;
322 drawing->gridSpacing = 12.0 / Painter::zoom;
323 zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
324 drawing->UpdateGridBackground();
329 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
331 if (exception != addArcAct)
332 addArcAct->setChecked(false);
334 if (exception != addCircleAct)
335 addCircleAct->setChecked(false);
337 if (exception != addDimensionAct)
338 addDimensionAct->setChecked(false);
340 if (exception != addLineAct)
341 addLineAct->setChecked(false);
343 if (exception != addPolygonAct)
344 addPolygonAct->setChecked(false);
346 if (exception != deleteAct)
347 deleteAct->setChecked(false);
349 if (exception != rotateAct)
350 rotateAct->setChecked(false);
354 void ApplicationWindow::SetInternalToolStates(void)
356 Object::SetDeleteActive(deleteAct->isChecked());
357 Object::SetDimensionActive(addDimensionAct->isChecked());
358 drawing->SetRotateToolActive(rotateAct->isChecked());
360 // We can be sure that if we've come here, then either an active tool is
361 // being deactivated, or a new tool is being created. In either case, the
362 // old tool needs to be deleted.
363 if (drawing->toolAction)
365 delete drawing->toolAction;
366 drawing->toolAction = NULL;
369 drawing->SetAddLineToolActive(addLineAct->isChecked());
370 drawing->SetAddCircleToolActive(addCircleAct->isChecked());
371 drawing->SetAddDimensionToolActive(addDimensionAct->isChecked());
375 void ApplicationWindow::HelpAbout(void)
381 void ApplicationWindow::Settings(void)
383 SettingsDialog dlg(this);
384 dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
386 if (dlg.exec() == false)
389 // Deal with stuff here (since user hit "OK" button...)
390 drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
396 // Group a bunch of selected objects (which can include other groups) together
397 // or ungroup a selected group.
399 void ApplicationWindow::HandleGrouping(void)
401 int itemsSelected = drawing->document.ItemsSelected();
403 // If nothing selected, do nothing
404 if (itemsSelected == 0)
406 statusBar()->showMessage(tr("No objects selected to make a group from."));
410 // If it's a group that's selected, ungroup it and leave the objects in a
412 if (itemsSelected == 1)
414 Object * object = drawing->document.SelectedItem(0);
418 printf("SelectedItem = NULL!\n");
420 printf("SelectedItem = %08X, type = %i\n", object, object->type);
423 if (object == NULL || object->type != OTContainer)
425 statusBar()->showMessage(tr("A group requires two or more selected objects."));
429 // Need the parent of the group, we're assuming here that the parent is
430 // the drawing's document. Does it matter? Maybe...
431 // Could just stipulate that grouping like this only takes place where the
432 // parent of the group is the drawing's document. Makes life much simpler.
433 ((Container *)object)->SelectAll();
434 ((Container *)object)->MoveContentsTo(&(drawing->document));
435 drawing->document.Delete(object);
436 statusBar()->showMessage(tr("Objects ungrouped."));
438 // Otherwise, if it's a group of 2 or more objects (which can be groups too)
439 // group them and select the group
440 else if (itemsSelected > 1)
442 Container * container = new Container(Vector(), &(drawing->document));
443 drawing->document.MoveSelectedContentsTo(container);
444 drawing->document.Add(container);
445 container->DeselectAll();
446 container->state = OSSelected;
447 statusBar()->showMessage(QString(tr("Grouped %1 objects.")).arg(itemsSelected));
454 void ApplicationWindow::CreateActions(void)
456 exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
457 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
458 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
460 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);
461 connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
463 fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
464 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
465 connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
467 fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
468 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
469 connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
471 deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(tr("Delete")), true);
472 connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
474 addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
475 connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
477 addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
478 connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
480 addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
481 connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
483 addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
484 connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
486 addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
487 connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
489 aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
490 connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
492 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);
493 connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
495 zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
496 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
498 zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
499 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
501 fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+n")));
502 connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
504 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")));
505 connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
507 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")));
508 connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
510 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")));
511 connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
513 fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+w")));
515 settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/generic-tool.png"), QKeySequence());
516 connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
518 groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g"));
519 connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
521 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
522 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
523 /* QActionGroup * group = new QActionGroup(this);
524 group->addAction(deleteAct);
525 group->addAction(addDimensionAct);
526 group->addAction(addLineAct);
527 group->addAction(addCircleAct);
528 group->addAction(addArcAct);//*/
533 // Consolidates action creation from a multi-step process to a single-step one.
535 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
536 QIcon icon, QKeySequence key, bool checkable/*= false*/)
538 QAction * action = new QAction(icon, name, this);
539 action->setToolTip(tooltip);
540 action->setStatusTip(statustip);
541 action->setShortcut(key);
542 action->setCheckable(checkable);
549 // This is essentially the same as the previous function, but this allows more
550 // than one key sequence to be added as key shortcuts.
552 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
553 QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
555 QAction * action = new QAction(icon, name, this);
556 action->setToolTip(tooltip);
557 action->setStatusTip(statustip);
558 QList<QKeySequence> keyList;
559 keyList.append(key1);
560 keyList.append(key2);
561 action->setShortcuts(keyList);
562 action->setCheckable(checkable);
568 void ApplicationWindow::CreateMenus(void)
570 QMenu * menu = menuBar()->addMenu(tr("&File"));
571 menu->addAction(fileNewAct);
572 menu->addAction(fileOpenAct);
573 menu->addAction(fileSaveAct);
574 menu->addAction(fileSaveAsAct);
575 menu->addAction(fileCloseAct);
576 menu->addSeparator();
577 menu->addAction(exitAct);
579 menu = menuBar()->addMenu(tr("&View"));
580 menu->addAction(zoomInAct);
581 menu->addAction(zoomOutAct);
583 menu = menuBar()->addMenu(tr("&Edit"));
584 menu->addAction(snapToGridAct);
585 menu->addAction(groupAct);
586 menu->addAction(fixAngleAct);
587 menu->addAction(fixLengthAct);
588 menu->addAction(rotateAct);
589 menu->addSeparator();
590 menu->addAction(deleteAct);
591 menu->addSeparator();
592 menu->addAction(addLineAct);
593 menu->addAction(addCircleAct);
594 menu->addAction(addArcAct);
595 menu->addAction(addPolygonAct);
596 menu->addAction(addDimensionAct);
597 menu->addSeparator();
598 menu->addAction(settingsAct);
600 menu = menuBar()->addMenu(tr("&Help"));
601 menu->addAction(aboutAct);
605 void ApplicationWindow::CreateToolbars(void)
607 QToolBar * toolbar = addToolBar(tr("File"));
608 toolbar->addAction(exitAct);
610 toolbar = addToolBar(tr("View"));
611 toolbar->addAction(zoomInAct);
612 toolbar->addAction(zoomOutAct);
614 toolbar = addToolBar(tr("Edit"));
615 toolbar->addAction(snapToGridAct);
616 toolbar->addAction(groupAct);
617 toolbar->addAction(fixAngleAct);
618 toolbar->addAction(fixLengthAct);
619 toolbar->addAction(rotateAct);
620 toolbar->addAction(deleteAct);
621 toolbar->addSeparator();
622 toolbar->addAction(addLineAct);
623 toolbar->addAction(addCircleAct);
624 toolbar->addAction(addArcAct);
625 toolbar->addAction(addPolygonAct);
626 toolbar->addAction(addDimensionAct);
630 void ApplicationWindow::ReadSettings(void)
632 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
633 QSize size = settings.value("size", QSize(400, 400)).toSize();
634 drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
635 snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
638 // pos = settings.value("charWndPos", QPoint(0, 0)).toPoint();
639 // size = settings.value("charWndSize", QSize(200, 200)).toSize();
640 // ((TTEdit *)qApp)->charWnd->resize(size);
641 // ((TTEdit *)qApp)->charWnd->move(pos);
645 void ApplicationWindow::WriteSettings(void)
647 settings.setValue("pos", pos());
648 settings.setValue("size", size());
649 settings.setValue("useAntialiasing", drawing->useAntialiasing);
650 settings.setValue("snapToGrid", snapToGridAct->isChecked());
651 // settings.setValue("charWndPos", ((TTEdit *)qApp)->charWnd->pos());
652 // settings.setValue("charWndSize", ((TTEdit *)qApp)->charWnd->size());