]> Shamusworld >> Repos - architektonas/blob - src/applicationwindow.cpp
Added Architektonas drawing file loading/saving infrastructure.
[architektonas] / src / applicationwindow.cpp
1 //
2 // applicationwindow.cpp: Architektonas
3 //
4 // Part of the Architektonas Project
5 // (C) 2011 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // Who  When        What
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
15 //
16
17 // FIXED:
18 //
19 //
20 // STILL TO BE DONE:
21 //
22 //
23
24 // Uncomment this for debugging...
25 //#define DEBUG
26 //#define DEBUGFOO                      // Various tool debugging...
27 //#define DEBUGTP                               // Toolpalette debugging...
28
29 #include "applicationwindow.h"
30
31 #include "about.h"
32 #include "drawingview.h"
33 #include "fileio.h"
34 #include "generaltab.h"
35 #include "painter.h"
36 #include "settingsdialog.h"
37
38
39 ApplicationWindow::ApplicationWindow(): settings("Underground Software", "Architektonas")
40 {
41         drawing = new DrawingView(this);
42         drawing->setMouseTracking(true);                // We want *all* mouse events...!
43         setCentralWidget(drawing);
44
45         aboutWin = new AboutWindow(this);
46
47 //      ((TTEdit *)qApp)->charWnd = new CharWindow(this);
48
49         setWindowIcon(QIcon(":/res/atns-icon.png"));
50         setWindowTitle("Architektonas");
51
52         CreateActions();
53         CreateMenus();
54         CreateToolbars();
55
56         //      Create status bar
57         zoomIndicator = new QLabel("Grid: 12.0\" Zoom: 12.5%");
58         statusBar()->addPermanentWidget(zoomIndicator);
59         statusBar()->showMessage(tr("Ready"));
60
61         ReadSettings();
62
63 //      connect(textEdit->document(), SIGNAL(contentsChanged()),
64 //                      this, SLOT(documentWasModified()));
65
66 //      setCurrentFile("");
67         setUnifiedTitleAndToolBarOnMac(true);
68
69 //      ((TTEdit *)qApp)->charWnd->show();//eh?
70         Object::SetFont(new QFont("Verdana", 15, QFont::Bold));
71 }
72
73
74 void ApplicationWindow::closeEvent(QCloseEvent * event)
75 {
76         WriteSettings();
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???
79 }
80
81
82 void ApplicationWindow::FileNew(void)
83 {
84         // Should warn the user if drawing hasn't been saved...
85         drawing->document.Clear();
86         drawing->update();
87         documentName.clear();
88         setWindowTitle("Architektonas - Untitled");
89         statusBar()->showMessage(tr("New drawing is ready."));
90 }
91
92
93 void ApplicationWindow::FileOpen(void)
94 {
95         QString filename = QFileDialog::getOpenFileName(this, tr("Open Drawing"),
96                 "", tr("Architektonas files (*.drawing)"));
97         FILE * file = fopen(filename.toAscii().data(), "r");
98
99         if (file == 0)
100         {
101                 QMessageBox msg;
102                 msg.setText(QString(tr("Could not open file \"%1\" for loading!")).arg(filename));
103                 msg.setIcon(QMessageBox::Critical);
104                 msg.exec();
105                 return;
106         }
107
108         Container container(Vector(0, 0));
109         bool successful = FileIO::LoadAtnsFile(file, &container);
110         fclose(file);
111
112         if (!successful)
113         {
114                 QMessageBox msg;
115                 msg.setText(QString(tr("Could not load file \"%1\"!")).arg(filename));
116                 msg.setIcon(QMessageBox::Critical);
117                 msg.exec();
118                 return;
119         }
120
121         drawing->document = container;
122         drawing->update();
123         documentName = filename;
124         setWindowTitle(QString("Architektonas - %1").arg(documentName));
125         statusBar()->showMessage(tr("Drawing loaded."));
126 }
127
128
129 void ApplicationWindow::FileSave(void)
130 {
131         if (documentName.isEmpty())
132                 documentName = QFileDialog::getSaveFileName(this, tr("Save Drawing"),
133                         "", tr("Architektonas drawings (*.drawing)"));
134
135         FILE * file = fopen(documentName.toAscii().data(), "w");
136
137         if (file == 0)
138         {
139                 QMessageBox msg;
140                 msg.setText(QString(tr("Could not open file \"%1\" for saving!")).arg(documentName));
141                 msg.setIcon(QMessageBox::Critical);
142                 msg.exec();
143                 return;
144         }
145
146         bool successful = FileIO::SaveAtnsFile(file, &drawing->document);
147         fclose(file);
148
149         if (!successful)
150         {
151                 QMessageBox msg;
152                 msg.setText(QString(tr("Could not save file \"%1\"!")).arg(documentName));
153                 msg.setIcon(QMessageBox::Critical);
154                 msg.exec();
155                 // In this case, we should unlink the created file, since it's not right...
156                 unlink(documentName.toAscii().data());
157                 return;
158         }
159
160         setWindowTitle(QString("Architektonas - %1").arg(documentName));
161         statusBar()->showMessage(tr("Drawing saved."));
162 }
163
164
165 void ApplicationWindow::FileSaveAs(void)
166 {
167         QString filename = QFileDialog::getSaveFileName(this, tr("Save Drawing As"),
168                 documentName, tr("Architektonas drawings (*.drawing)"));
169
170         if (!filename.isEmpty())
171         {
172                 documentName = filename;
173                 FileSave();
174         }
175 }
176
177
178 void ApplicationWindow::SnapToGridTool(void)
179 {
180         Object::SetSnapMode(snapToGridAct->isChecked());
181 }
182
183
184 void ApplicationWindow::FixAngle(void)
185 {
186         Object::SetFixedAngle(fixAngleAct->isChecked());
187 }
188
189
190 void ApplicationWindow::FixLength(void)
191 {
192         Object::SetFixedLength(fixLengthAct->isChecked());
193 }
194
195
196 // We want certain tools to be exclusive, and this approach isn't working correctly...
197 void ApplicationWindow::DeleteTool(void)
198 {
199         
200         ClearUIToolStatesExcept(deleteAct);
201         SetInternalToolStates();
202 }
203
204
205 void ApplicationWindow::DimensionTool(void)
206 {
207         ClearUIToolStatesExcept(addDimensionAct);
208         SetInternalToolStates();
209 }
210
211
212 void ApplicationWindow::RotateTool(void)
213 {
214         ClearUIToolStatesExcept(rotateAct);
215         SetInternalToolStates();
216 }
217
218
219 void ApplicationWindow::AddLineTool(void)
220 {
221         ClearUIToolStatesExcept(addLineAct);
222         SetInternalToolStates();
223 }
224
225
226 void ApplicationWindow::AddCircleTool(void)
227 {
228         ClearUIToolStatesExcept(addCircleAct);
229         SetInternalToolStates();
230 }
231
232
233 void ApplicationWindow::AddArcTool(void)
234 {
235         ClearUIToolStatesExcept(addArcAct);
236         SetInternalToolStates();
237 }
238
239
240 void ApplicationWindow::AddPolygonTool(void)
241 {
242         ClearUIToolStatesExcept(addPolygonAct);
243         SetInternalToolStates();
244 }
245
246
247 void ApplicationWindow::ZoomInTool(void)
248 {
249         double zoomFactor = 2.0;
250 /*
251 We need to find the center of the screen, then figure out where the new corner
252 will be in the zoomed in window.
253
254 So we know in Qt coords, the center is found via:
255 size.width()  / 2 --> xCenter
256 size.height() / 2 --> yCenter
257
258 transform x/yCenter to Cartesian coordinates. So far, so good.
259
260 when zooming in, new origin will be (xCenter - origin.x) / 2, (yCenter - origin.y) / 2
261 (after subtracting from center, that is...)
262 */
263         QSize size = drawing->size();
264         Vector center(size.width() / 2.0, size.height() / 2.0);
265 //printf("Zoom in... Center=%.2f,%.2f; ", center.x, center.y);
266         center = Painter::QtToCartesianCoords(center);
267 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
268         Vector newOrigin = center - ((center - Painter::origin) / zoomFactor);
269 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
270         Painter::origin = newOrigin;
271
272 //printf("Zoom in... level going from %02f to ", Painter::zoom);
273         // This just zooms leaving origin intact... should zoom in at the current center! [DONE]
274         Painter::zoom *= zoomFactor;
275         drawing->gridSpacing = 12.0 / Painter::zoom;
276         zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
277         drawing->UpdateGridBackground();
278         drawing->update();
279 }
280
281
282 void ApplicationWindow::ZoomOutTool(void)
283 {
284 /*
285 Ok, real example.
286 center = (436, 311)
287 origin = (223, 160.5)
288 newOrigin should be (-10, -10)
289 Why isn't it?
290
291 center - origin = (213, 150.5)
292 origin - center = (-213, -150.5)
293 x 2 = (-426, -301)
294 + center = (-10, -10)
295
296 */
297         double zoomFactor = 2.0;
298         QSize size = drawing->size();
299         Vector center(size.width() / 2.0, size.height() / 2.0);
300 //printf("Zoom out... Center=%.2f,%.2f; ", center.x, center.y);
301         center = Painter::QtToCartesianCoords(center);
302 //printf("(%.2f,%.2f); origin=%.2f,%.2f; ", center.x, center.y, Painter::origin.x, Painter::origin.y);
303 //      Vector newOrigin = (center - Painter::origin) * zoomFactor;
304 //      Vector newOrigin = center - (Painter::origin * zoomFactor);
305         Vector newOrigin = center + ((Painter::origin - center) * zoomFactor);
306 //printf("newOrigin=%.2f,%.2f;\n", newOrigin.x, newOrigin.y);
307         Painter::origin = newOrigin;
308 //printf("Zoom out...\n");
309         // This just zooms leaving origin intact... should zoom out at the current center! [DONE]
310         Painter::zoom /= zoomFactor;
311         drawing->gridSpacing = 12.0 / Painter::zoom;
312         zoomIndicator->setText(QString("Grid: %2\" Zoom: %1%").arg(Painter::zoom * 100.0 * SCREEN_ZOOM).arg(drawing->gridSpacing));
313         drawing->UpdateGridBackground();
314         drawing->update();
315 }
316
317
318 void ApplicationWindow::ClearUIToolStatesExcept(QAction * exception)
319 {
320         if (exception != addArcAct)
321                 addArcAct->setChecked(false);
322
323         if (exception != addCircleAct)
324                 addCircleAct->setChecked(false);
325
326         if (exception != addDimensionAct)
327                 addDimensionAct->setChecked(false);
328
329         if (exception != addLineAct)
330                 addLineAct->setChecked(false);
331
332         if (exception != addPolygonAct)
333                 addPolygonAct->setChecked(false);
334
335         if (exception != deleteAct)
336                 deleteAct->setChecked(false);
337
338         if (exception != rotateAct)
339                 rotateAct->setChecked(false);
340 }
341
342
343 void ApplicationWindow::SetInternalToolStates(void)
344 {
345         Object::SetDeleteActive(deleteAct->isChecked());
346         Object::SetDimensionActive(addDimensionAct->isChecked());
347         drawing->SetRotateToolActive(rotateAct->isChecked());
348         drawing->SetAddLineToolActive(addLineAct->isChecked());
349         drawing->SetAddCircleToolActive(addCircleAct->isChecked());
350 }
351
352
353 void ApplicationWindow::HelpAbout(void)
354 {
355         aboutWin->show();
356 }
357
358
359 void ApplicationWindow::Settings(void)
360 {
361         SettingsDialog dlg(this);
362         dlg.generalTab->antialiasChk->setChecked(drawing->useAntialiasing);
363
364         if (dlg.exec() == false)
365                 return;
366
367         // Deal with stuff here (since user hit "OK" button...)
368         drawing->useAntialiasing = dlg.generalTab->antialiasChk->isChecked();
369         WriteSettings();
370 }
371
372
373 void ApplicationWindow::HandleGrouping(void)
374 {
375         // Group a bunch of selected objects together or ungroup a selected group.
376 }
377
378
379 void ApplicationWindow::CreateActions(void)
380 {
381         exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."),
382                 QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
383         connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
384
385         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);
386         connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
387
388         fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
389                 QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
390         connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
391
392         fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
393                 QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
394         connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
395
396         deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(), true);
397         connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
398
399         addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
400         connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
401
402         addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
403         connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
404
405         addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
406         connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
407
408         addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
409         connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
410
411         addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
412         connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
413
414         aboutAct = CreateAction(tr("About &Architektonas"), tr("About Architektonas"), tr("Gives information about this program."), QIcon(":/res/generic-tool.png"), QKeySequence());
415         connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
416
417         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);
418         connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
419
420         zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
421         connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
422
423         zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
424         connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
425
426         fileNewAct = CreateAction(tr("&New Drawing"), tr("New Drawing"), tr("Creates a new drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+n")));
427         connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
428
429         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")));
430         connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
431
432         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")));
433         connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
434
435         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")));
436         connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
437
438         fileCloseAct = CreateAction(tr("&Close Drawing"), tr("Close Drawing"), tr("Closes the current drawing."), QIcon(":/res/generic-tool.png"), QKeySequence(tr("Ctrl+w")));
439
440         settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/generic-tool.png"), QKeySequence());
441         connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
442
443         groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/generic-tool.png"), QKeySequence("g"));
444         connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
445
446 //Hm. I think we'll have to have separate logic to do the "Radio Group Toolbar" thing...
447 // Yup, in order to turn them off, we'd have to have an "OFF" toolbar button. Ick.
448 /*      QActionGroup * group = new QActionGroup(this);
449         group->addAction(deleteAct);
450         group->addAction(addDimensionAct);
451         group->addAction(addLineAct);
452         group->addAction(addCircleAct);
453         group->addAction(addArcAct);//*/
454 }
455
456
457 //
458 // Consolidates action creation from a multi-step process to a single-step one.
459 //
460 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
461         QIcon icon, QKeySequence key, bool checkable/*= false*/)
462 {
463         QAction * action = new QAction(icon, name, this);
464         action->setToolTip(tooltip);
465         action->setStatusTip(statustip);
466         action->setShortcut(key);
467         action->setCheckable(checkable);
468
469         return action;
470 }
471
472
473 //
474 // This is essentially the same as the previous function, but this allows more
475 // than one key sequence to be added as key shortcuts.
476 //
477 QAction * ApplicationWindow::CreateAction(QString name, QString tooltip, QString statustip,
478         QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
479 {
480         QAction * action = new QAction(icon, name, this);
481         action->setToolTip(tooltip);
482         action->setStatusTip(statustip);
483         QList<QKeySequence> keyList;
484         keyList.append(key1);
485         keyList.append(key2);
486         action->setShortcuts(keyList);
487         action->setCheckable(checkable);
488
489         return action;
490 }
491
492
493 void ApplicationWindow::CreateMenus(void)
494 {
495         QMenu * menu = menuBar()->addMenu(tr("&File"));
496         menu->addAction(fileNewAct);
497         menu->addAction(fileOpenAct);
498         menu->addAction(fileSaveAct);
499         menu->addAction(fileSaveAsAct);
500         menu->addAction(fileCloseAct);
501         menu->addSeparator();
502         menu->addAction(exitAct);
503
504         menu = menuBar()->addMenu(tr("&View"));
505         menu->addAction(zoomInAct);
506         menu->addAction(zoomOutAct);
507
508         menu = menuBar()->addMenu(tr("&Edit"));
509         menu->addAction(snapToGridAct);
510         menu->addAction(groupAct);
511         menu->addAction(fixAngleAct);
512         menu->addAction(fixLengthAct);
513         menu->addAction(rotateAct);
514         menu->addSeparator();
515         menu->addAction(deleteAct);
516         menu->addSeparator();
517         menu->addAction(addLineAct);
518         menu->addAction(addCircleAct);
519         menu->addAction(addArcAct);
520         menu->addAction(addPolygonAct);
521         menu->addAction(addDimensionAct);
522         menu->addSeparator();
523         menu->addAction(settingsAct);
524
525         menu = menuBar()->addMenu(tr("&Help"));
526         menu->addAction(aboutAct);
527 }
528
529
530 void ApplicationWindow::CreateToolbars(void)
531 {
532         QToolBar * toolbar = addToolBar(tr("File"));
533         toolbar->addAction(exitAct);
534
535         toolbar = addToolBar(tr("View"));
536         toolbar->addAction(zoomInAct);
537         toolbar->addAction(zoomOutAct);
538
539         toolbar = addToolBar(tr("Edit"));
540         toolbar->addAction(snapToGridAct);
541         toolbar->addAction(groupAct);
542         toolbar->addAction(fixAngleAct);
543         toolbar->addAction(fixLengthAct);
544         toolbar->addAction(rotateAct);
545         toolbar->addAction(deleteAct);
546         toolbar->addSeparator();
547         toolbar->addAction(addLineAct);
548         toolbar->addAction(addCircleAct);
549         toolbar->addAction(addArcAct);
550         toolbar->addAction(addPolygonAct);
551         toolbar->addAction(addDimensionAct);
552 }
553
554
555 void ApplicationWindow::ReadSettings(void)
556 {
557         QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
558         QSize size = settings.value("size", QSize(400, 400)).toSize();
559         drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
560         snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
561         resize(size);
562         move(pos);
563 //      pos = settings.value("charWndPos", QPoint(0, 0)).toPoint();
564 //      size = settings.value("charWndSize", QSize(200, 200)).toSize();
565 //      ((TTEdit *)qApp)->charWnd->resize(size);
566 //      ((TTEdit *)qApp)->charWnd->move(pos);
567 }
568
569
570 void ApplicationWindow::WriteSettings(void)
571 {
572         settings.setValue("pos", pos());
573         settings.setValue("size", size());
574         settings.setValue("useAntialiasing", drawing->useAntialiasing);
575         settings.setValue("snapToGrid", snapToGridAct->isChecked());
576 //      settings.setValue("charWndPos", ((TTEdit *)qApp)->charWnd->pos());
577 //      settings.setValue("charWndSize", ((TTEdit *)qApp)->charWnd->size());
578 }
579