1 // Copyright (c) 2008  GeometryFactory Sarl (France).
2 // All rights reserved.
3 //
4 // This file is part of CGAL (www.cgal.org).
5 //
6 // $URL: https://github.com/CGAL/cgal/blob/v5.3/GraphicsView/include/CGAL/Qt/DemosMainWindow_impl.h $
7 // $Id: DemosMainWindow_impl.h c9a020b 2021-02-09T12:12:56+01:00 Maxime Gimeno
8 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
9 //
10 //
11 // Author(s)     : Andreas Fabri <Andreas.Fabri@geometryfactory.com>
12 //                 Laurent Rineau <Laurent.Rineau@geometryfactory.com>
13 
14 #ifdef CGAL_HEADER_ONLY
15 #define CGAL_INLINE_FUNCTION inline
16 
17 #include <CGAL/license/GraphicsView.h>
18 
19 #else
20 #define CGAL_INLINE_FUNCTION
21 #endif
22 
23 #include <CGAL/Qt/GraphicsViewNavigation.h>
24 #include <QApplication>
25 #include <QLabel>
26 #include <QFile>
27 #include <QFileDialog>
28 #include <QFileInfo>
29 #include <QMenu>
30 #include <QMenuBar>
31 #include <QAction>
32 #include <QMessageBox>
33 #include <QStatusBar>
34 #include <QGraphicsView>
35 #include <QGLWidget>
36 #include <QTextStream>
37 #include <QSettings>
38 #include <QUrl>
39 #include <QDesktopWidget>
40 #include <QRegExp>
41 #include <QSvgGenerator>
42 #include <QtCore>
43 #include <QtOpenGL>
44 
45 #include <CGAL/config.h> // needed to get CGAL_VERSION_STR
46 #include <CGAL/Qt/DemosMainWindow.h>
47 #include <iostream>
48 
49 namespace CGAL {
50 namespace Qt {
51 
52 CGAL_INLINE_FUNCTION
DemosMainWindow(QWidget * parent,::Qt::WindowFlags flags)53 DemosMainWindow::DemosMainWindow(QWidget * parent, ::Qt::WindowFlags flags)
54   : QMainWindow(parent, flags),
55     maxNumRecentFiles(10),
56     recentFileActs(maxNumRecentFiles)
57 {
58   // prepare the QLabel xycoord for inclusion in the statusBar()
59   xycoord = new QLabel(" -0.00000 , -0.00000 ", this);
60   xycoord->setAlignment(::Qt::AlignHCenter);
61   xycoord->setMinimumSize(xycoord->sizeHint());
62   xycoord->clear();
63 
64   actionUse_OpenGL = new QAction(this);
65   actionUse_OpenGL->setObjectName("actionUse_OpenGL");
66   actionUse_OpenGL->setCheckable(true);
67   actionUse_OpenGL->setText(tr("Use &OpenGL"));
68   actionUse_OpenGL->setStatusTip(tr("Make Qt use OpenGL to display the graphical items, instead of its native painting system."));
69   actionUse_OpenGL->setShortcut(tr("Ctrl+G"));
70 
71   actionUse_Antialiasing = new QAction(this);
72   actionUse_Antialiasing->setObjectName("actionUse_Antialiasing");
73   actionUse_Antialiasing->setCheckable(true);
74   actionUse_Antialiasing->setText(tr("Use &anti-aliasing"));
75   actionUse_Antialiasing->setStatusTip(tr("Make Qt use anti-aliasing when displaying the graphical items."));
76   actionUse_Antialiasing->setShortcut(tr("Ctrl+A"));
77 
78   actionAboutCGAL = new QAction(this);
79   actionAboutCGAL->setObjectName("actionAboutCGAL");
80   actionAboutCGAL->setText(tr("About &CGAL..."));
81 
82   actionAbout = new QAction(this);
83   actionAbout->setObjectName("actionAbout");
84   actionAbout->setText(tr("&About..."));
85 
86   setAcceptDrops(true);
87 }
88 
89 CGAL_INLINE_FUNCTION
~DemosMainWindow()90 DemosMainWindow::~DemosMainWindow()
91 {
92 }
93 
94 CGAL_INLINE_FUNCTION
95 void
dragEnterEvent(QDragEnterEvent * event)96 DemosMainWindow::dragEnterEvent(QDragEnterEvent *event)
97 {
98   if (event->mimeData()->hasFormat("text/uri-list"))
99     event->acceptProposedAction();
100 }
101 
102 CGAL_INLINE_FUNCTION
103 void
dropEvent(QDropEvent * event)104 DemosMainWindow::dropEvent(QDropEvent *event)
105 {
106   Q_FOREACH(QUrl url, event->mimeData()->urls()) {
107     QString filename = url.toLocalFile();
108     this->open(filename);
109   }
110   event->acceptProposedAction();
111 }
112 
113 CGAL_INLINE_FUNCTION
114 void
addNavigation(QGraphicsView * graphicsView)115 DemosMainWindow::addNavigation(QGraphicsView* graphicsView)
116 {
117   navigation = new CGAL::Qt::GraphicsViewNavigation();
118   graphicsView->viewport()->installEventFilter(navigation);
119   graphicsView->installEventFilter(navigation);
120   QObject::connect(navigation, SIGNAL(mouseCoordinates(QString)),
121                    xycoord, SLOT(setText(QString)));
122   view = graphicsView;
123 }
124 
125 CGAL_INLINE_FUNCTION
126 void
setupStatusBar()127 DemosMainWindow::setupStatusBar()
128 {
129   this->statusBar()->addWidget(new QLabel(this), 1);
130   this->statusBar()->addWidget(xycoord, 0);
131 }
132 
133 CGAL_INLINE_FUNCTION
134 void
setupOptionsMenu(QMenu * menuOptions)135 DemosMainWindow::setupOptionsMenu(QMenu* menuOptions)
136 {
137   // search for the Options menu
138   if(!menuOptions) {
139     menuOptions = getMenu("menuOptions", tr("&Options"));
140   }
141 
142   // if not found, then create it
143   if(!menuOptions) {
144     menuOptions = new QMenu(this->menuBar());
145     menuOptions->setTitle(tr("&Options"));
146     this->menuBar()->addAction(menuOptions->menuAction());
147     menuOptions->setObjectName("menuOptions");
148   }
149 
150   if(!menuOptions->isEmpty()) {
151     menuOptions->addSeparator();
152   }
153   menuOptions->addAction(actionUse_OpenGL);
154   menuOptions->addAction(actionUse_Antialiasing);
155   connect(actionUse_Antialiasing, SIGNAL(toggled(bool)),
156           this, SLOT(setUseAntialiasing(bool)));
157   connect(actionUse_OpenGL, SIGNAL(toggled(bool)),
158           this, SLOT(setUseOpenGL(bool)));
159   actionUse_Antialiasing->setChecked(true);
160 }
161 
162 CGAL_INLINE_FUNCTION
163 void
setupExportSVG(QAction * action,QGraphicsView * view)164 DemosMainWindow::setupExportSVG(QAction* action, QGraphicsView* view)
165 {
166   this->view = view;
167   connect(action, SIGNAL(triggered(bool)),
168           this, SLOT(exportSVG()));
169 }
170 
171 CGAL_INLINE_FUNCTION
exportSVG()172 void DemosMainWindow::exportSVG()
173 {
174   QString fileName = QFileDialog::getSaveFileName(this,
175                                                   tr("Export to SVG"),
176                                                   ".",
177                                                   tr("SVG (*.svg)\n"));
178 
179   QSvgGenerator svg;
180   svg.setFileName(fileName);
181 
182   svg.setSize(this->view->size());
183   svg.setViewBox(this->view->sceneRect());
184   svg.setTitle(tr("%1 drawing").arg(qApp->applicationName()));
185   svg.setDescription(tr("Generated using %1").arg(qApp->applicationName()));
186 
187   QPainter painter;
188   painter.begin(&svg);
189   this->view->render(&painter);
190   painter.end();
191 }
192 
193 CGAL_INLINE_FUNCTION
194 void
setUseAntialiasing(bool checked)195 DemosMainWindow::setUseAntialiasing(bool checked)
196 {
197   view->setRenderHint(QPainter::Antialiasing, checked);
198 
199   statusBar()->showMessage(tr("Antialiasing %1activated").arg(checked?"":"de-"),
200                            1000);
201 }
202 
203 CGAL_INLINE_FUNCTION
204 void
setUseOpenGL(bool checked)205 DemosMainWindow::setUseOpenGL(bool checked)
206 {
207   if(checked) {
208     QGLWidget* new_viewport = new QGLWidget;
209 
210     // Setup the format to allow antialiasing with OpenGL:
211     // one need to activate the SampleBuffers, if the graphic driver allows
212     // this.
213     QGLFormat glformat = new_viewport->format();
214     glformat.setOption(QGL::SampleBuffers);
215     new_viewport->setFormat(glformat);
216 
217     view->setViewport(new_viewport);
218   }
219   else {
220     view->setViewport(new QWidget);
221   }
222   statusBar()->showMessage(tr("OpenGL %1activated").arg(checked?"":"de-"),
223                            1000);
224   view->viewport()->installEventFilter(navigation);
225   view->setFocus();
226 }
227 
228 CGAL_INLINE_FUNCTION
229 QMenu*
getMenu(QString objectName,QString title)230 DemosMainWindow::getMenu(QString objectName, QString title)
231 {
232   QMenu* menu = nullptr;
233 
234   QString title2 = title;
235   title2.remove('&');
236   // search if a menu has objectName()==objectName
237   menu = this->findChild<QMenu*>(objectName);
238 
239   // then search if a menu has title()==title
240   if(menu) {
241     return menu;
242   } else {
243     Q_FOREACH(menu, this->findChildren<QMenu*>()) {
244       if(menu->title() == title ||
245          menu->title() == title2) {
246         return menu;
247       }
248     }
249   }
250   return nullptr;
251 }
252 
253 CGAL_INLINE_FUNCTION
254 void
popupAboutBox(QString title,QString html_resource_name)255 DemosMainWindow::popupAboutBox(QString title, QString html_resource_name)
256 {
257   QFile about_CGAL(html_resource_name);
258   about_CGAL.open(QIODevice::ReadOnly);
259   QString about_CGAL_txt = QTextStream(&about_CGAL).readAll();
260 #ifdef CGAL_VERSION_STR
261   QString cgal_version(CGAL_VERSION_STR);
262 #  ifdef CGAL_FAKE_PUBLIC_RELEASE
263   cgal_version.replace(QRegExp("-Ic?.*"), "");
264 #  endif
265   about_CGAL_txt.replace("<!--CGAL_VERSION-->",
266                          QString(" (version %1)")
267                          .arg(cgal_version));
268 #endif
269   QMessageBox mb(QMessageBox::NoIcon,
270                  title,
271                  about_CGAL_txt,
272                  QMessageBox::Ok,
273                  this);
274 
275   QLabel* mb_label = mb.findChild<QLabel*>("qt_msgbox_label");
276   if(mb_label) {
277     mb_label->setTextInteractionFlags(mb_label->textInteractionFlags() |
278                                       ::Qt::LinksAccessibleByMouse |
279                                       ::Qt::LinksAccessibleByKeyboard);
280   }
281   else {
282     std::cerr << "Cannot find child \"qt_msgbox_label\" in QMessageBox\n"
283               << "  with Qt version " << QT_VERSION_STR << "!\n";
284   }
285   mb.exec();
286 }
287 
288 CGAL_INLINE_FUNCTION
getHelpMenu()289 QMenu* DemosMainWindow::getHelpMenu()
290 {
291   QMenu* menuHelp = getMenu("menuHelp", tr("&Help"));
292   if(!menuHelp) {
293     menuHelp = new QMenu(this->menuBar());
294     menuHelp->setTitle(tr("&Help"));
295     this->menuBar()->addAction(menuHelp->menuAction());
296     menuHelp->setObjectName("menuHelp");
297   }
298   return menuHelp;
299 }
300 
301 CGAL_INLINE_FUNCTION
302 void
addAboutCGAL(QMenu * menuHelp)303 DemosMainWindow::addAboutCGAL(QMenu* menuHelp)
304 {
305   if(!menuHelp) {
306     menuHelp = getHelpMenu();
307   }
308   menuHelp->addAction(actionAboutCGAL);
309 
310   connect(actionAboutCGAL, SIGNAL(triggered()),
311           this, SLOT(popupAboutCGAL()));
312 }
313 
314 CGAL_INLINE_FUNCTION
315 void
addAboutDemo(QString htmlResourceName,QMenu * menuHelp)316 DemosMainWindow::addAboutDemo(QString htmlResourceName, QMenu* menuHelp)
317 {
318   if(!menuHelp) {
319     menuHelp = getHelpMenu();
320   }
321   menuHelp->addAction(actionAbout);
322   aboutHtmlResource = htmlResourceName;
323 
324   connect(actionAbout, SIGNAL(triggered()),
325           this, SLOT(popupAboutDemo()));
326 }
327 
328 CGAL_INLINE_FUNCTION
329 void
popupAboutCGAL()330 DemosMainWindow::popupAboutCGAL()
331 {
332   popupAboutBox(tr("About CGAL..."),
333                 ":/cgal/help/about_CGAL.html");
334 }
335 
336 CGAL_INLINE_FUNCTION
337 void
popupAboutDemo()338 DemosMainWindow::popupAboutDemo()
339 {
340   popupAboutBox(tr("About the demo..."),
341                 aboutHtmlResource);
342 }
343 
344 CGAL_INLINE_FUNCTION
345 void
setMaxNumberOfRecentFiles(const unsigned int i)346 DemosMainWindow::setMaxNumberOfRecentFiles(const unsigned int i)
347 {
348   maxNumRecentFiles = i;
349   recentFileActs.resize(maxNumRecentFiles);
350 }
351 
352 CGAL_INLINE_FUNCTION
353 unsigned int
maxNumberOfRecentFiles()354 DemosMainWindow::maxNumberOfRecentFiles() const
355 {
356   return maxNumRecentFiles;
357 }
358 
359 CGAL_INLINE_FUNCTION
360 void
openRecentFile_aux()361 DemosMainWindow::openRecentFile_aux()
362 {
363   QAction *action = qobject_cast<QAction *>(sender());
364   if (action)
365     Q_EMIT openRecentFile(action->data().toString());
366 }
367 
368 CGAL_INLINE_FUNCTION
369 void
addToRecentFiles(QString fileName)370 DemosMainWindow::addToRecentFiles(QString fileName)
371 {
372   QStringList files = settings.value("recentFileList").toStringList();
373   files.removeAll(fileName);
374   files.prepend(fileName);
375   while (files.size() > (int)maxNumberOfRecentFiles())
376     files.removeLast();
377   settings.setValue("recentFileList", files);
378 
379   updateRecentFileActions();
380 }
381 
382 CGAL_INLINE_FUNCTION
383 void
addRecentFiles(QMenu * menu,QAction * insertBeforeAction)384 DemosMainWindow::addRecentFiles(QMenu* menu, QAction* insertBeforeAction)
385 {
386   if(!insertBeforeAction) {
387     recentFilesSeparator = menu->addSeparator();
388   }
389 
390   for (unsigned int i = 0; i < maxNumberOfRecentFiles(); ++i) {
391     recentFileActs[i] = new QAction(this);
392     recentFileActs[i]->setVisible(false);
393     connect(recentFileActs[i], SIGNAL(triggered()),
394             this, SLOT(openRecentFile_aux()));
395     if(insertBeforeAction)
396       menu->insertAction(insertBeforeAction, recentFileActs[i]);
397     else
398       menu->addAction(recentFileActs[i]);
399   }
400 
401   if(insertBeforeAction) {
402     recentFilesSeparator = menu->insertSeparator(insertBeforeAction);
403   }
404 
405   recentFilesSeparator->setVisible(false);
406 
407   updateRecentFileActions();
408 }
409 
410 CGAL_INLINE_FUNCTION
411 void
updateRecentFileActions()412 DemosMainWindow::updateRecentFileActions()
413 {
414   QStringList files = settings.value("recentFileList").toStringList();
415 
416   int numRecentFiles = qMin(files.size(), (int)this->maxNumberOfRecentFiles());
417 
418   for (int i = 0; i < numRecentFiles; ++i) {
419     QString strippedName = QFileInfo(files[i]).fileName();
420     QString text = tr("&%1 %2").arg(i).arg(strippedName);
421     recentFileActs[i]->setText(text);
422     recentFileActs[i]->setData(files[i]);
423     recentFileActs[i]->setVisible(true);
424   }
425   for (unsigned int j = numRecentFiles; j < maxNumberOfRecentFiles(); ++j)
426     recentFileActs[j]->setVisible(false);
427 
428   recentFilesSeparator->setVisible(numRecentFiles > 0);
429 }
430 
431 CGAL_INLINE_FUNCTION
writeState(QString groupname)432 void DemosMainWindow::writeState(QString groupname)
433 {
434 
435 
436   settings.beginGroup(groupname);
437   settings.setValue("size", size());
438   settings.setValue("pos", pos());
439   settings.setValue("state", saveState());
440   settings.endGroup();
441 }
442 
443 CGAL_INLINE_FUNCTION
readState(QString groupname,Options)444 void DemosMainWindow::readState(QString groupname, Options /*what_to_save*/)
445 {
446 
447 
448   settings.beginGroup(groupname);
449   resize(settings.value("size", this->size()).toSize());
450 
451   QPoint pos = settings.value("pos", this->pos()).toPoint();
452 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
453   if(QGuiApplication::screenAt(pos)) {
454 #else
455    QDesktopWidget* desktop = qApp->desktop();
456      if(desktop->availableGeometry(pos).contains(pos)) {
457 #endif
458 
459     move(pos);
460   }
461   settings.endGroup();
462 }
463 
464 
465 } // namespace Qt
466 } // namespace CGAL
467