1 
2 #ifdef CGAL_USE_SSH
3 #  include "CGAL/Use_ssh.h"
4 #endif
5 #include <cmath>
6 
7 #include "config.h"
8 #include "MainWindow.h"
9 #include "Scene.h"
10 #include <CGAL/Three/Scene_item.h>
11 #include <CGAL/Three/TextRenderer.h>
12 #include <CGAL/Three/exceptions.h>
13 #include <CGAL/Three/Three.h>
14 #include <CGAL/Qt/debug.h>
15 #include <CGAL/double.h>
16 
17 #include <QJsonArray>
18 #include <QtDebug>
19 #include <QFileDialog>
20 #include <QFileInfo>
21 #include <QHeaderView>
22 #include <QMenu>
23 #include <QMenuBar>
24 #include <QChar>
25 #include <QAction>
26 #include <QShortcut>
27 #include <QKeySequence>
28 #include <QLibrary>
29 #include <QPluginLoader>
30 #include <QMessageBox>
31 #include <QScrollBar>
32 #include <QColor>
33 #include <QColorDialog>
34 #include <QClipboard>
35 #include <QCloseEvent>
36 #include <QInputDialog>
37 #include <QTreeView>
38 #include <QSortFilterProxyModel>
39 #include <QStandardItemModel>
40 #include <QStandardItem>
41 #include <QTreeWidgetItem>
42 #include <QTreeWidget>
43 #include <QDockWidget>
44 #include <QSpinBox>
45 #include <stdexcept>
46 #include <fstream>
47 #include <QElapsedTimer>
48 #include <QWidgetAction>
49 #include <QJsonArray>
50 #include <QSequentialIterable>
51 #include <QDir>
52 #ifdef QT_SCRIPT_LIB
53 #  include <QScriptValue>
54 #  ifdef QT_SCRIPTTOOLS_LIB
55 #    include <QScriptEngineDebugger>
56 #  endif
57 #endif
58 
59 #include <CGAL/Three/Three.h>
60 #include <CGAL/Three/Polyhedron_demo_plugin_interface.h>
61 #include <CGAL/Three/Polyhedron_demo_io_plugin_interface.h>
62 #include <CGAL/Three/Scene_item_with_properties.h>
63 #include "ui_SubViewer.h"
64 #include "ui_MainWindow.h"
65 #include "ui_Preferences.h"
66 #include "ui_Details.h"
67 #include "ui_Statistics_on_item_dialog.h"
68 #include "ui_SSH_dialog.h"
69 #include "Show_point_dialog.h"
70 #include "File_loader_dialog.h"
71 #include "Viewer.h"
72 
73 #include <CGAL/Qt/manipulatedCameraFrame.h>
74 #include <CGAL/Qt/manipulatedFrame.h>
75 
76 #ifdef QT_SCRIPT_LIB
77 #  include <QScriptEngine>
78 #  include <QScriptValue>
79 #include "Color_map.h"
80 
81 
82 using namespace CGAL::Three;
83 QScriptValue
myScene_itemToScriptValue(QScriptEngine * engine,CGAL::Three::Scene_item * const & in)84 myScene_itemToScriptValue(QScriptEngine *engine,
85                           CGAL::Three::Scene_item* const &in)
86 {
87   return engine->newQObject(in);
88 }
89 
myScene_itemFromScriptValue(const QScriptValue & object,CGAL::Three::Scene_item * & out)90 void myScene_itemFromScriptValue(const QScriptValue &object,
91                                  CGAL::Three::Scene_item* &out)
92 {
93   out = qobject_cast<CGAL::Three::Scene_item*>(object.toQObject());
94 }
95 #endif // QT_SCRIPT_LIB
96 
97 #ifdef QT_SCRIPT_LIB
98 #  ifdef QT_SCRIPTTOOLS_LIB
99 
100 const QScriptEngineDebugger::DebuggerWidget debug_widgets[9] = {
101   QScriptEngineDebugger::ConsoleWidget,
102   QScriptEngineDebugger::StackWidget,
103   QScriptEngineDebugger::ScriptsWidget,
104   QScriptEngineDebugger::LocalsWidget,
105   QScriptEngineDebugger::CodeWidget,
106   QScriptEngineDebugger::CodeFinderWidget,
107   QScriptEngineDebugger::BreakpointsWidget,
108   QScriptEngineDebugger::DebugOutputWidget,
109   QScriptEngineDebugger::ErrorLogWidget
110 };
111 const QString debug_widgets_names[9] = {
112   "Script console",
113   "Stack",
114   "Scripts",
115   "Locals",
116   "Code",
117   "CodeFinder",
118   "Breakpoints",
119   "DebugOutput",
120   "ErrorLog"
121 };
122 
123 #  endif
124 #endif
125 
myPrintFunction(QScriptContext * context,QScriptEngine * engine)126 QScriptValue myPrintFunction(QScriptContext *context, QScriptEngine *engine)
127 {
128   MainWindow* mw = qobject_cast<MainWindow*>(engine->parent());
129   QString result;
130   for (int i = 0; i < context->argumentCount(); ++i) {
131     if (i > 0)
132       result.append(" ");
133     result.append(context->argument(i).toString());
134   }
135 
136   if(mw) mw->message(QString("QtScript: ") + result, "");
137   QTextStream (stdout) << (QString("QtScript: ") + result) << "\n";
138 
139   return engine->undefinedValue();
140 }
141 
~MainWindow()142 MainWindow::~MainWindow()
143 {
144   searchAction->deleteLater();
145   delete ui;
146   delete statistics_ui;
147 }
MainWindow(const QStringList & keywords,bool verbose,QWidget * parent)148 MainWindow::MainWindow(const QStringList &keywords, bool verbose, QWidget* parent)
149   : CGAL::Qt::DemosMainWindow(parent),
150     accepted_keywords(keywords)
151 {
152   bbox_need_update = true;
153   ui = new Ui::MainWindow;
154   ui->setupUi(this);
155   menuBar()->setNativeMenuBar(false);
156   searchAction = new QWidgetAction(nullptr);
157   CGAL::Three::Three::s_mainwindow = this;
158   menu_map[ui->menuOperations->title()] = ui->menuOperations;
159   this->verbose = verbose;
160   // remove the Load Script menu entry, when the demo has not been compiled with QT_SCRIPT_LIB
161 #if !defined(QT_SCRIPT_LIB)
162   ui->menuBar->removeAction(ui->actionLoadScript);
163   ui->menuBar->removeAction(ui->on_actionLoad_a_Scene_from_a_Script_File);
164 #endif
165   // Save some pointers from ui, for latter use.
166   sceneView = ui->sceneView;
167   viewer_window = new SubViewer(ui->mdiArea, this, nullptr);
168   viewer = viewer_window->viewer;
169   CGAL::Three::Three::s_mainviewer = viewer;
170   viewer->setObjectName("mainViewer");
171   viewer_window->showMaximized();
172   viewer_window->setWindowFlags(
173         Qt::SubWindow
174         | Qt::CustomizeWindowHint
175         | Qt::WindowMaximizeButtonHint
176         | Qt::WindowSystemMenuHint
177         | Qt::WindowTitleHint
178         );
179   viewer_window->setWindowTitle("Main Viewer");
180   // setup scene
181   scene = new Scene(this);
182   CGAL::Three::Three::s_scene = scene;
183   CGAL::Three::Three::s_connectable_scene = scene;
184   {
185     QShortcut* shortcut = new QShortcut(QKeySequence(Qt::ALT+Qt::Key_Q), this);
186     connect(shortcut, SIGNAL(activated()),
187             this, SLOT(setFocusToQuickSearch()));
188     shortcut = new QShortcut(QKeySequence(Qt::Key_F5), this);
189     connect(shortcut, SIGNAL(activated()),
190             this, SLOT(reloadItem()));
191     shortcut = new QShortcut(QKeySequence(Qt::Key_F11), this);
192     connect(shortcut, SIGNAL(activated()),
193             this, SLOT(toggleFullScreen()));
194     shortcut = new QShortcut(QKeySequence(Qt::CTRL+Qt::Key_R), this);
195     connect(shortcut, &QShortcut::activated,
196             this, &MainWindow::recenterScene);
197     shortcut = new QShortcut(QKeySequence(Qt::CTRL+Qt::Key_T), this);
198     connect(shortcut, &QShortcut::activated,
199             this,
200             [](){
201       Viewer* viewer = qobject_cast<Viewer*>(CGAL::Three::Three::activeViewer());
202       bool b = viewer->property("draw_two_sides").toBool();
203       viewer->setTwoSides(!b);
204     }
205             );
206   }
207 
208   proxyModel = new QSortFilterProxyModel(this);
209   proxyModel->setSourceModel(scene);
210   SceneDelegate *delegate = new SceneDelegate(this);
211   delegate->setProxy(proxyModel);
212   delegate->setScene(scene);
213 
214 
215   connect(ui->searchEdit, SIGNAL(textChanged(QString)),
216           proxyModel, SLOT(setFilterFixedString(QString)));
217   sceneView->setModel(proxyModel);
218 
219   // setup the sceneview: delegation and columns sizing...
220   sceneView->setItemDelegate(delegate);
221   resetHeader();
222 
223   // setup connections
224   connect(scene, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex & )),
225           this, SLOT(updateInfo()));
226 
227   connect(scene, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex & )),
228           this, SLOT(updateDisplayInfo()));
229 
230   connect(viewer, &Viewer::needNewContext,
231           [this](){create();});
232 
233 
234   connect(scene, SIGNAL(updated()),
235           this, SLOT(selectionChanged()));
236 
237   connect(scene, SIGNAL(itemAboutToBeDestroyed(CGAL::Three::Scene_item*)),
238           this, SLOT(removeManipulatedFrame(CGAL::Three::Scene_item*)));
239 
240   connect(scene, SIGNAL(updated_bbox(bool)),
241           this, SLOT(invalidate_bbox(bool)));
242 
243   connect(scene, SIGNAL(selectionChanged(int)),
244           this, SLOT(selectSceneItem(int)));
245   connect(scene, SIGNAL(selectionChanged(QList<int>)),
246           this, SLOT(selectSceneItems(QList<int>)));
247 
248   connect(scene, SIGNAL(itemPicked(const QModelIndex &)),
249           this, SLOT(recenterSceneView(const QModelIndex &)));
250 
251   connect(sceneView->selectionModel(),
252           SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & ) ),
253           this, SLOT(updateInfo()));
254 
255   connect(sceneView->selectionModel(),
256           SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & ) ),
257           this, SLOT(updateDisplayInfo()));
258 
259   connect(sceneView->selectionModel(),
260           SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & ) ),
261           this, SLOT(selectionChanged()));
262   // setup menu filtering
263 
264   connect(sceneView->selectionModel(),
265       QOverload<const QItemSelection & , const QItemSelection &>::of(&QItemSelectionModel::selectionChanged),
266       this, [this](){filterOperations(false);});
267 
268   sceneView->setContextMenuPolicy(Qt::CustomContextMenu);
269   connect(sceneView, SIGNAL(customContextMenuRequested(const QPoint & )),
270           this, SLOT(showSceneContextMenu(const QPoint &)));
271 
272   connect(sceneView, SIGNAL(expanded(QModelIndex)),
273           this, SLOT(setExpanded(QModelIndex)));
274 
275   connect(sceneView, SIGNAL(collapsed(QModelIndex)),
276           this, SLOT(setCollapsed(QModelIndex)));
277   connect(this, SIGNAL(collapsed(QModelIndex)),
278           scene, SLOT(setCollapsed(QModelIndex)));
279   connect(this, SIGNAL(expanded(QModelIndex)),
280           scene, SLOT(setExpanded(QModelIndex)));
281 
282   connect(scene, SIGNAL(restoreCollapsedState()),
283           this, SLOT(restoreCollapseState()));
284 
285   setupViewer(viewer, viewer_window);
286 
287   // add the "About CGAL..." and "About demo..." entries
288   this->addAboutCGAL();
289   this->addAboutDemo(":/cgal/Polyhedron_3/about.html");
290 
291   // Connect the button "addButton" with actionLoad
292   ui->addButton->setDefaultAction(ui->actionLoad);
293   // Same with "removeButton" and "duplicateButton"
294   ui->removeButton->setDefaultAction(ui->actionErase);
295   ui->duplicateButton->setDefaultAction(ui->actionDuplicate);
296 
297   // Connect actionQuit (Ctrl+Q) and qApp->quit()
298   connect(ui->actionQuit, SIGNAL(triggered()),
299           this, SLOT(quit()));
300   // Connect "Select all items"
301   connect(ui->actionSelectAllItems, SIGNAL(triggered()),
302           this, SLOT(selectAll()));
303 
304   connect(ui->actionColorItems, SIGNAL(triggered()),
305           this, SLOT(colorItems()));
306 
307   // Recent files menu
308   this->addRecentFiles(ui->menuFile, ui->actionQuit);
309   connect(this, SIGNAL(openRecentFile(QString)),
310           this, SLOT(open(QString)));
311 
312   // Reset the "Operation menu"
313   clearMenu(ui->menuOperations);
314 
315 #ifdef QT_SCRIPT_LIB
316   std::cerr << "Enable scripts.\n";
317   script_engine = new QScriptEngine(this);
318   qScriptRegisterMetaType<CGAL::Three::Scene_item*>(script_engine,
319                                                     myScene_itemToScriptValue,
320                                                     myScene_itemFromScriptValue);
321 #  ifdef QT_SCRIPTTOOLS_LIB
322   QScriptEngineDebugger* debugger = new QScriptEngineDebugger(this);
323   debugger->setObjectName("qt script debugger");
324   QAction* debuggerMenuAction =
325       menuBar()->addMenu(debugger->createStandardMenu());
326   debuggerMenuAction->setText(tr("Qt Script &Debug"));
327   for(unsigned int i = 0; i < 9; ++i)
328   {
329     QDockWidget* dock = new QDockWidget(debug_widgets_names[i], this);
330     dock->setObjectName(debug_widgets_names[i]);
331     dock->setWidget(debugger->widget(debug_widgets[i]));
332     this->QMainWindow::addDockWidget(Qt::BottomDockWidgetArea, dock);
333     dock->hide();
334   }
335   debugger->setAutoShowStandardWindow(false);
336   debugger->attachTo(script_engine);
337 #  endif // QT_SCRIPTTOOLS_LIB
338   QScriptValue fun = script_engine->newFunction(myPrintFunction);
339   script_engine->globalObject().setProperty("print", fun);
340 
341   //  evaluate_script("print('hello', 'world', 'from QtScript!')");
342   QScriptValue mainWindowObjectValue = script_engine->newQObject(this);
343   script_engine->globalObject().setProperty("main_window", mainWindowObjectValue);
344 
345   QScriptValue sceneObjectValue = script_engine->newQObject(scene);
346   mainWindowObjectValue.setProperty("scene", sceneObjectValue);
347   script_engine->globalObject().setProperty("scene", sceneObjectValue);
348 
349   QScriptValue viewerObjectValue = script_engine->newQObject(viewer);
350   mainWindowObjectValue.setProperty("viewer", viewerObjectValue);
351   script_engine->globalObject().setProperty("viewer", viewerObjectValue);
352 
353   QScriptValue cameraObjectValue = script_engine->newQObject(viewer->camera());
354   viewerObjectValue.setProperty("camera", cameraObjectValue);
355   script_engine->globalObject().setProperty("camera", cameraObjectValue);
356 
357   evaluate_script("var plugins = new Array();");
358 #  ifdef QT_SCRIPTTOOLS_LIB
359   QScriptValue debuggerObjectValue = script_engine->newQObject(debugger);
360   script_engine->globalObject().setProperty("debugger", debuggerObjectValue);
361 #  endif
362 #endif
363 
364   readSettings(); // Among other things, the column widths are stored.
365 
366   // Load plugins, and re-enable actions that need it.
367   operationSearchBar.setPlaceholderText("Filter...");
368   searchAction->setDefaultWidget(&operationSearchBar);
369 
370   connect(&operationSearchBar, &QLineEdit::textChanged,
371           this, [this](){filterOperations(true);});
372 
373   loadPlugins();
374   accepted_keywords.clear();
375 
376   // Setup the submenu of the View menu that can toggle the dockwidgets
377   Q_FOREACH(QDockWidget* widget, findChildren<QDockWidget*>()) {
378     widget->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable);
379     ui->menuDockWindows->addAction(widget->toggleViewAction());
380   }
381   ui->menuDockWindows->removeAction(ui->dummyAction);
382 
383 
384   this->readState("MainWindow", Size|State);
385 
386   //Manages the group_item creation
387   actionAddToGroup= new QAction("Add New Group", this);
388 
389   if(actionAddToGroup) {
390     connect(actionAddToGroup, SIGNAL(triggered()),
391             this, SLOT(makeNewGroup()));
392   }
393 
394   QMenu* menuFile = findChild<QMenu*>("menuFile");
395   insertActionBeforeLoadPlugin(menuFile, actionAddToGroup);
396   statistics_dlg = nullptr;
397   statistics_ui = new Ui::Statistics_on_item_dialog();
398 
399   actionResetDefaultLoaders = new QAction("Reset Default Loaders",this);
400 
401 #ifdef QT_SCRIPT_LIB
402   // evaluate_script("print(plugins);");
403   Q_FOREACH(QAction* action, findChildren<QAction*>()) {
404     if(action->objectName() != "") {
405       QScriptValue objectValue = script_engine->newQObject(action);
406       script_engine->globalObject().setProperty(action->objectName(),
407                                                 objectValue);
408     }
409   }
410   filterOperations(true);
411   // debugger->action(QScriptEngineDebugger::InterruptAction)->trigger();
412 #endif
413 }
414 
addActionToMenu(QAction * action,QMenu * menu)415 void addActionToMenu(QAction* action, QMenu* menu)
416 {
417   auto actions = menu->actions();
418   auto it = std::lower_bound(actions.begin(), actions.end(),
419                              action->text().remove("&"),
420                              [](QAction* a, QString text) {
421                                return a->text().remove("&").compare(text) < 0;
422                              });
423   if(it == actions.end()) {
424     menu->addAction(action);
425   }
426   else {
427     menu->insertAction(*it, action);
428   }
429 }
430 
431 //Recursive function that do a pass over a menu and its sub-menus(etc.) and hide them when they are empty
filterMenuOperations(QMenu * menu,QString filter,bool keep_from_here)432 void filterMenuOperations(QMenu* menu, QString filter, bool keep_from_here)
433 {
434   QList<QAction*> buffer;
435   Q_FOREACH(QAction* action, menu->actions())
436     buffer.append(action);
437 
438   while(!buffer.isEmpty()){
439     Q_FOREACH(QAction* action, buffer) {
440       if(QMenu* submenu = action->menu())
441       {
442         bool keep = true;
443         if(!keep_from_here){
444           keep = submenu->menuAction()->text().contains(filter, Qt::CaseInsensitive);
445           if(!keep)
446           {
447             Q_FOREACH(QAction* subaction, submenu->actions())
448             {
449               submenu->removeAction(subaction);
450               buffer.append(subaction);
451             }
452           }
453           else
454           {
455             addActionToMenu(submenu->menuAction(), menu);
456           }
457         }
458         filterMenuOperations(submenu, filter, keep);
459         action->setVisible(!(submenu->isEmpty()));
460 
461       }
462       else if(action->text().remove("&").contains(filter, Qt::CaseInsensitive)){
463         //menu->addAction(action);
464         addActionToMenu(action, menu);
465       }
466       buffer.removeAll(action);
467     }
468   }
469 }
470 
471 #ifdef Q_OS_WIN
filterOperations(bool hide)472 void MainWindow::filterOperations(bool hide)
473 #else
474 void MainWindow::filterOperations(bool)
475 #endif
476 {
477   //on some platforms editing an open menu slows everything like hell,
478   //so we hide it for the time of the process.
479 #ifdef Q_OS_WIN
480   if(hide)
481     ui->menuOperations->hide();
482 #endif
483   //return actions to their true menu
484   Q_FOREACH(QMenu* menu, action_menu_map.values())
485   {
486     Q_FOREACH(QAction* action, menu->actions())
487     {
488       if(action != searchAction)
489         menu->removeAction(action);
490     }
491   }
492 
493   Q_FOREACH(QAction* action, action_menu_map.keys())
494   {
495     QMenu* menu = action_menu_map[action];
496     addActionToMenu(action, menu);
497   }
498   QString filter=operationSearchBar.text();
499   Q_FOREACH(const PluginNamePair& p, plugins) {
500     Q_FOREACH(QAction* action, p.first->actions()) {
501       action->setVisible( p.first->applicable(action)
502                           && (action->text().remove("&").contains(filter, Qt::CaseInsensitive)
503                               || action->property("subMenuName")
504                               .toString().contains(filter, Qt::CaseInsensitive)));
505     }
506   }
507   // do a pass over all menus in Operations and their sub-menus(etc.) and hide them when they are empty
508   filterMenuOperations(ui->menuOperations, filter, false);
509 #ifdef Q_OS_WIN
510   if(hide)
511     ui->menuOperations->show();
512 #endif
513   operationSearchBar.setFocus();
514 }
515 
516 #include <CGAL/Three/exceptions.h>
517 
evaluate_script(QString script,const QString & filename,const bool quiet)518 void MainWindow::evaluate_script(QString script,
519                                  const QString& filename,
520                                  const bool quiet) {
521   QScriptContext* context = script_engine->currentContext();
522   QScriptValue object = context->activationObject();
523   QScriptValue former_current_filename = object.property("current_filename");;
524   object.setProperty("current_filename", filename);
525 
526   QScriptValue value = script_engine->evaluate(script, filename);
527   if(script_engine->hasUncaughtException()) {
528     QScriptValue js_exception = script_engine->uncaughtException();
529     QScriptValue js_bt =js_exception.property("backtrace");
530     QStringList bt = script_engine->uncaughtExceptionBacktrace();
531     if(js_bt.isValid()) {
532       QStringList other_bt;
533       qScriptValueToSequence(js_bt, other_bt);
534       if(!other_bt.isEmpty()) bt = other_bt;
535     }
536     if(!quiet) {
537       QTextStream err(stderr);
538       err << "Qt Script exception:\n"
539           << js_exception.toString()
540           << "\nBacktrace:\n";
541       Q_FOREACH(QString line, bt) {
542         err << "  " << line << "\n";
543       }
544     }
545     throw CGAL::Three::Script_exception
546         (script_engine->uncaughtException().toString(), bt);
547   }
548   else if(!quiet && !value.isNull() && !value.isUndefined()) {
549     QTextStream(stderr) << "Qt Script evaluated to \""
550                         << value.toString() << "\"\n";
551   }
552 
553   object.setProperty("current_filename", former_current_filename);
554 }
555 
evaluate_script_quiet(QString script,const QString & filename)556 void MainWindow::evaluate_script_quiet(QString script,
557                                        const QString& filename)
558 {
559   evaluate_script(script, filename, true);
560 }
561 
enableScriptDebugger(bool b)562 void MainWindow::enableScriptDebugger(bool b /* = true */)
563 {
564   Q_UNUSED(b);
565 #ifdef QT_SCRIPT_LIB
566 #  ifdef QT_SCRIPTTOOLS_LIB
567   QScriptEngineDebugger* debugger =
568       findChild<QScriptEngineDebugger*>("qt script debugger");
569   if(debugger) {
570     if(b) {
571       debugger->action(QScriptEngineDebugger::InterruptAction)->trigger();
572     }
573     else {
574       std::cerr << "Detach the script debugger\n";
575       debugger->detach();
576     }
577   }
578   return;
579 #  endif
580 #endif
581   // If we are here, then the debugger is not available
582   this->error(tr("Your version of Qt is too old, and for that reason "
583                  "the Qt Script Debugger is not available."));
584 }
585 
586 namespace {
actionsByName(QAction * x,QAction * y)587 bool actionsByName(QAction* x, QAction* y) {
588   return x->text() < y->text();
589 }
590 }
591 
592 //Recursively creates all subMenus containing an action.
593 // In the current implementation, there is a bug if a menu
594 // and a submenu have the same name (cf map menu_map).
setMenus(QString name,QString parentName,QAction * a)595 void MainWindow::setMenus(QString name, QString parentName, QAction* a )
596 {
597   QString menuName, subMenuName;
598   if (name.isNull())
599     return;
600   int slash_index = name.indexOf('/');
601 
602   if(slash_index==-1)
603     menuName= name; // no extra sub-menu
604   else
605   {
606     int l = name.length();
607     menuName=name.mid(0,slash_index);
608     subMenuName=name.mid(slash_index+1,l-slash_index-1);
609     // recursively create sub-menus
610     setMenus(subMenuName, menuName, a);
611   }
612 
613   //Create the menu if it does not already exist
614   if(!menu_map.contains(menuName))
615     menu_map[menuName] = new QMenu(menuName, this);
616 
617   //Create the parent menu if it does not already exist
618   if(!menu_map.contains(parentName))
619     menu_map[parentName] = new QMenu(parentName, this);
620   // add the submenu in the menu
621   menu_map[parentName]->addMenu(menu_map[menuName]);
622   action_menu_map[menu_map[menuName]->menuAction()] = menu_map[parentName];
623 
624   // only add the action in the last submenu
625   if(slash_index==-1)
626   {
627     ui->menuOperations->removeAction(a);
628     menu_map[menuName]->addAction(a);
629     action_menu_map[a] = menu_map[menuName];
630   }
631 }
632 
load_plugin(QString fileName,bool blacklisted)633 bool MainWindow::load_plugin(QString fileName, bool blacklisted)
634 {
635   if(fileName.contains("plugin") && QLibrary::isLibrary(fileName)) {
636     //set plugin name
637     QFileInfo fileinfo(fileName);
638     //set plugin name
639     QString name = fileinfo.fileName();
640     name.remove(QRegExp("^lib"));
641     name.remove(QRegExp("\\..*"));
642     //do not load it if it is in the blacklist
643     if(blacklisted)
644     {
645       if ( plugin_blacklist.contains(name) ){
646         pluginsStatus_map[name] = QString("Blacklisted.");
647         ignored_map[name] = true;
648         //qDebug("### Ignoring plugin \"%s\".", qPrintable(fileName));
649         PathNames_map[name].push_back(fileinfo.absoluteDir().absolutePath());
650         return true;
651       }
652     }
653     QDebug qdebug = qDebug();
654     if(verbose)
655       qdebug << "### Loading \"" << fileName.toUtf8().data() << "\"... ";
656     QPluginLoader loader;
657     loader.setFileName(fileinfo.absoluteFilePath());
658     QJsonArray keywords = loader.metaData().value("MetaData").toObject().value("Keywords").toArray();
659     QString date = loader.metaData().value("MetaData").toObject().value("ConfigDate").toString();
660     QStringList s_keywords;
661     for(int i = 0; i < keywords.size(); ++i)
662     {
663       s_keywords.append(keywords[i].toString());
664     }
665     plugin_metadata_map[name] = qMakePair(s_keywords, date);
666     QObject *obj = loader.instance();
667     bool do_load = accepted_keywords.empty();
668     if(!do_load)
669     {
670       Q_FOREACH(QString k, s_keywords)
671       {
672         if(accepted_keywords.contains(k))
673         {
674           do_load = true;
675           break;
676         }
677       }
678     }
679     if(do_load && obj) {
680       obj->setObjectName(name);
681       bool init1 = initPlugin(obj);
682       bool init2 = initIOPlugin(obj);
683       if (!init1 && !init2)
684       {
685         //qdebug << "not for this program";
686         pluginsStatus_map[name] = QString("Not for this program.");
687       }
688       else
689         //qdebug << "success";
690         pluginsStatus_map[name] = QString("success");
691     }
692     else if(!do_load)
693     {
694       pluginsStatus_map[name]="Wrong Keywords.";
695       ignored_map[name] = true;
696     }
697     else{
698       //qdebug << "error: " << qPrintable(loader.errorString());
699       pluginsStatus_map[name] = loader.errorString();
700 
701     }
702     PathNames_map[name].push_back(fileinfo.absoluteDir().absolutePath());
703     return true;
704   }
705   return false;
706 }
707 
loadPlugins()708 void MainWindow::loadPlugins()
709 {
710   Q_FOREACH(QObject *obj, QPluginLoader::staticInstances())
711   {
712     initPlugin(obj);
713     initIOPlugin(obj);
714   }
715 
716   QList<QDir> plugins_directories;
717   QString dirPath = qApp->applicationDirPath();
718   plugins_directories<<dirPath;
719   QDir msvc_dir(dirPath);
720   QString build_dir_name = msvc_dir.dirName();//Debug or Release for msvc
721   msvc_dir.cdUp();
722 
723   QFileInfoList filist = QDir(dirPath).entryInfoList();
724   filist << msvc_dir.entryInfoList();
725 
726   Q_FOREACH(QFileInfo fileinfo, filist)
727   {
728     //checks if the path leads to a directory
729     if(fileinfo.baseName().contains("Plugins"))
730     {
731       QString plugins_dir = fileinfo.absolutePath();
732       plugins_dir.append("/").append(fileinfo.baseName());
733 
734       Q_FOREACH(QString package_dir,
735                 QDir(plugins_dir).entryList(QDir::Dirs))
736       {
737         QString package_dir_path(plugins_dir);
738         package_dir_path.append("/").append(package_dir);
739 
740         QString libdir_path(package_dir_path);
741         libdir_path.append("/").append(build_dir_name);
742 
743         if (QDir(libdir_path).exists())
744           plugins_directories << QDir(libdir_path);
745         else
746           plugins_directories << QDir(package_dir_path);
747       }
748     }
749   }
750   QString env_path = qgetenv("POLYHEDRON_DEMO_PLUGINS_PATH");
751 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
752   QChar separator = QDir::listSeparator();
753 #else
754 #if defined(_WIN32)
755   QChar separator = ';';
756 #else
757   QChar separator = ':';
758 #endif
759 #endif
760   if(!env_path.isEmpty()) {
761 #if defined(_WIN32)
762     QString path = qgetenv("PATH");
763     QByteArray new_path = path.append(env_path.prepend(separator)).toUtf8();
764     qputenv("PATH", new_path);
765 #endif
766     Q_FOREACH (QString pluginsDir,
767                env_path.split(separator, CGAL_QT_SKIP_EMPTY_PARTS)) {
768       QDir dir(pluginsDir);
769       if(dir.isReadable())
770         plugins_directories << dir;
771     }
772   }
773 
774   QSet<QString> loaded;
775   Q_FOREACH (QDir pluginsDir, plugins_directories) {
776     if(verbose)
777       qDebug("# Looking for plugins in directory \"%s\"...",
778              qPrintable(pluginsDir.absolutePath()));
779     Q_FOREACH(QString fileName, pluginsDir.entryList(QDir::Files))
780     {
781       QString abs_name = pluginsDir.absoluteFilePath(fileName);
782       if(loaded.find(abs_name) == loaded.end())
783       {
784         if(load_plugin(abs_name, true))
785         {
786           loaded.insert(abs_name);
787         }
788       }
789     }
790   }
791   updateMenus();
792 }
793 //Creates sub-Menus for operations.
updateMenus()794 void MainWindow::updateMenus()
795 {
796   QList<QAction*> as = ui->menuOperations->actions();
797   Q_FOREACH(QAction* a, as)
798   {
799     QString menuPath = a->property("subMenuName").toString();
800     setMenus(menuPath, ui->menuOperations->title(), a);
801   }
802   // sort the operations menu by name
803   as = ui->menuOperations->actions();
804   std::sort(as.begin(), as.end(), actionsByName);
805   ui->menuOperations->clear();
806   ui->menuOperations->addAction(searchAction);
807   ui->menuOperations->addActions(as);
808   operationSearchBar.setFocus();
809 }
810 
hasPlugin(const QString & pluginName) const811 bool MainWindow::hasPlugin(const QString& pluginName) const
812 {
813   Q_FOREACH(const PluginNamePair& p, plugins) {
814     if(p.second == pluginName) return true;
815   }
816   return false;
817 }
818 
initPlugin(QObject * obj)819 bool MainWindow::initPlugin(QObject* obj)
820 {
821   QObjectList childs = this->children();
822   CGAL::Three::Polyhedron_demo_plugin_interface* plugin =
823       qobject_cast<CGAL::Three::Polyhedron_demo_plugin_interface*>(obj);
824   if(plugin) {
825     // Call plugin's init() method
826     obj->setParent(this);
827     plugin->init(this, this->scene, this);
828     plugins << qMakePair(plugin, obj->objectName());
829 #ifdef QT_SCRIPT_LIB
830     QScriptValue objectValue =
831         script_engine->newQObject(obj);
832     script_engine->globalObject().setProperty(obj->objectName(), objectValue);
833     evaluate_script_quiet(QString("plugins.push(%1);").arg(obj->objectName()));
834 #endif
835 
836     Q_FOREACH(QAction* action, plugin->actions()) {
837       // If action does not belong to the menus, add it to "Operations" menu
838       if(!childs.contains(action)) {
839         ui->menuOperations->addAction(action);
840         action_menu_map[action] = ui->menuOperations;
841       }
842       // Show and enable menu item
843       addAction(action);
844     }
845     return true;
846   }
847   else
848     return false;
849 }
850 
initIOPlugin(QObject * obj)851 bool MainWindow::initIOPlugin(QObject* obj)
852 {
853   CGAL::Three::Polyhedron_demo_io_plugin_interface* plugin =
854       qobject_cast<CGAL::Three::Polyhedron_demo_io_plugin_interface*>(obj);
855   if(plugin) {
856     plugin->init();
857     io_plugins << plugin;
858     return true;
859   }
860   else
861     return false;
862 }
863 
clearMenu(QMenu * menu)864 void MainWindow::clearMenu(QMenu* menu)
865 {
866   Q_FOREACH(QAction* action, menu->actions())
867   {
868     QMenu* menu = action->menu();
869     if(menu) {
870       clearMenu(menu);
871     }
872     action->setVisible(false);
873   }
874   menu->menuAction()->setEnabled(false);
875 }
876 
addAction(QAction * action)877 void MainWindow::addAction(QAction* action)
878 {
879   if(!action) return;
880 
881   action->setVisible(true);
882   action->setEnabled(true);
883   Q_FOREACH(QWidget* widget, action->associatedWidgets())
884   {
885     //     qDebug() << QString("%1 (%2)\n")
886     //       .arg(widget->objectName())
887     //       .arg(widget->metaObject()->className());
888     QMenu* menu = qobject_cast<QMenu*>(widget);
889     if(menu)
890     {
891       addAction(menu->menuAction());
892     }
893   }
894 }
895 
addAction(QString actionName,QString actionText,QString menuName)896 void MainWindow::addAction(QString actionName,
897                            QString actionText,
898                            QString menuName) {
899   QMenu* menu = nullptr;
900   Q_FOREACH(QAction* action, findChildren<QAction*>()) {
901     if(!action->menu()) continue;
902     QString menuText = action->menu()->title();
903     if(menuText != menuName) continue;
904     menu = action->menu();
905   }
906   if(menu == nullptr) {
907     menu = new QMenu(menuName, this);
908     menuBar()->insertMenu(ui->menuView->menuAction(), menu);
909   }
910   QAction* action = new QAction(actionText, this);
911   action->setObjectName(actionName);
912   menu->addAction(action);
913 #ifdef QT_SCRIPT_LIB
914   QScriptValue objectValue = script_engine->newQObject(action);
915   script_engine->globalObject().setProperty(action->objectName(),
916                                             objectValue);
917 #endif
918 }
919 
viewerShow(float xmin,float ymin,float zmin,float xmax,float ymax,float zmax)920 void MainWindow::viewerShow(float xmin,
921                             float ymin,
922                             float zmin,
923                             float xmax,
924                             float ymax,
925                             float zmax)
926 {
927   CGAL::qglviewer::Vec
928       min_(xmin, ymin, zmin),
929       max_(xmax, ymax, zmax);
930 
931   if(min_ == max_) return viewerShow(viewer, xmin, ymin, zmin);
932 
933   viewer->camera()->setPivotPoint((min_+max_)*0.5);
934 
935   CGAL::qglviewer::ManipulatedCameraFrame backup_frame(*viewer->camera()->frame());
936   viewer->camera()->fitBoundingBox(min_, max_);
937   CGAL::qglviewer::ManipulatedCameraFrame new_frame(*viewer->camera()->frame());
938   *viewer->camera()->frame() = backup_frame;
939   viewer->camera()->interpolateTo(new_frame, 1.f);
940   viewer->setVisualHintsMask(1);
941 }
942 
viewerShow(Viewer_interface * vi,float x,float y,float z)943 void MainWindow::viewerShow(Viewer_interface* vi, float x, float y, float z) {
944 
945   CGAL::qglviewer::ManipulatedCameraFrame backup_frame(*vi->camera()->frame());
946   vi->camera()->fitSphere(CGAL::qglviewer::Vec(x, y, z),
947                           vi->camera()->sceneRadius()/100);
948   CGAL::qglviewer::ManipulatedCameraFrame new_frame(*vi->camera()->frame());
949   *vi->camera()->frame() = backup_frame;
950   vi->camera()->interpolateTo(new_frame, 1.f);
951   vi->setVisualHintsMask(1);
952 
953   vi->camera()->setPivotPoint(CGAL::qglviewer::Vec(x, y, z));
954 }
955 
message(QString message,QString colorName,QString font)956 void MainWindow::message(QString message, QString colorName, QString font) {
957   if (message.endsWith('\n')) {
958     message.remove(message.length()-1, 1);
959   }
960   statusBar()->showMessage(message, 5000);
961   QTimer::singleShot(5000, [this]{this->statusBar()->setStyleSheet("");});
962   message = "<font color=\"" + colorName + "\" style=\"font-style: " + font + ";\" >" +
963       message + "</font><br>";
964   message = "[" + QTime::currentTime().toString() + "] " + message;
965   ui->consoleTextEdit->append(message);
966   ui->consoleTextEdit->verticalScrollBar()->setValue(ui->consoleTextEdit->verticalScrollBar()->maximum());
967 }
968 
message_information(QString text)969 void MainWindow::message_information(QString text) {
970   statusBar()->setStyleSheet("color: blue");
971   this->message("INFO: " + text, "blue");
972 }
973 
message_warning(QString text)974 void MainWindow::message_warning(QString text) {
975   statusBar()->setStyleSheet("color: orange");
976   this->message("WARNING: " + text, "orange");
977 }
978 
message_error(QString text)979 void MainWindow::message_error(QString text) {
980   statusBar()->setStyleSheet("color: red");
981   this->message("ERROR: " + text, "red");
982 }
983 
updateViewersBboxes(bool recenter)984 void MainWindow::updateViewersBboxes(bool recenter)
985 {
986   if(bbox_need_update)
987   {
988   CGAL::qglviewer::Vec min, max;
989   computeViewerBBox(min, max);
990   Q_FOREACH(CGAL::QGLViewer* v, CGAL::QGLViewer::QGLViewerPool())
991   {
992     if(v == nullptr)
993       continue;
994     Viewer* vi = static_cast<Viewer*>(v);
995     updateViewerBbox(vi, recenter, min, max);
996   }
997   bbox_need_update = false;
998 }
999 
1000 }
1001 
computeViewerBBox(CGAL::qglviewer::Vec & vmin,CGAL::qglviewer::Vec & vmax)1002 void MainWindow::computeViewerBBox(CGAL::qglviewer::Vec& vmin, CGAL::qglviewer::Vec& vmax)
1003 {
1004   const Scene::Bbox bbox = scene->bbox();
1005   const double xmin = bbox.xmin();
1006   const double ymin = bbox.ymin();
1007   const double zmin = bbox.zmin();
1008   const double xmax = bbox.xmax();
1009   const double ymax = bbox.ymax();
1010   const double zmax = bbox.zmax();
1011 
1012 
1013 
1014   vmin = CGAL::qglviewer::Vec(xmin, ymin, zmin);
1015   vmax= CGAL::qglviewer::Vec(xmax, ymax, zmax);
1016 
1017   CGAL::qglviewer::Vec bbox_center((xmin+xmax)/2, (ymin+ymax)/2, (zmin+zmax)/2);
1018 
1019   double bbox_diag = CGAL::approximate_sqrt(
1020         CGAL::square(xmax - xmin)
1021         + CGAL::square(ymax - ymin)
1022         + CGAL::square(zmax - zmin));
1023 
1024   CGAL::qglviewer::Vec offset(0,0,0);
1025 
1026   double l_dist = (std::max)((std::abs)(bbox_center.x - viewer->offset().x),
1027                              (std::max)((std::abs)(bbox_center.y - viewer->offset().y),
1028                                         (std::abs)(bbox_center.z - viewer->offset().z)));
1029   if((std::log2)(l_dist/bbox_diag) > 11.0 )
1030     for(int i=0; i<3; ++i)
1031     {
1032       offset[i] = -bbox_center[i];
1033     }
1034   if(offset != viewer->offset())
1035   {
1036     Q_FOREACH(CGAL::QGLViewer* v, CGAL::QGLViewer::QGLViewerPool())
1037     {
1038       if(v == nullptr)
1039         continue;
1040       Viewer* vi = qobject_cast<Viewer*>(v);
1041       vi->setOffset(offset);
1042     }
1043     for(int i=0; i<scene->numberOfEntries(); ++i)
1044     {
1045       //      scene->item(i)->invalidate(Scene_item::GEOMETRY);
1046       scene->item(i)->invalidateOpenGLBuffers();
1047       scene->item(i)->itemChanged();
1048     }
1049   }
1050 }
1051 
reloadItem()1052 void MainWindow::reloadItem() {
1053 
1054   Scene_item* item = nullptr;
1055 
1056   Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
1057   {
1058     item = scene->item(id);
1059     if(!item)//secure items like selection items that get deleted when their "parent" item is reloaded.
1060       continue;
1061     QString filename = item->property("source filename").toString();
1062     QString loader_name = item->property("loader_name").toString();
1063     if(filename.isEmpty() || loader_name.isEmpty()) {
1064       this->warning(QString("Cannot reload item %1: "
1065                             "the item has no \"source filename\" or no \"loader_name\" attached\n").arg(item->name()));
1066       continue;
1067     }
1068 
1069     CGAL::Three::Polyhedron_demo_io_plugin_interface* fileloader = findLoader(loader_name);
1070     QFileInfo fileinfo(filename);
1071     bool ok;
1072     QList<Scene_item*> new_items = loadItem(fileinfo, fileloader, ok, false);
1073     if(!ok)
1074       return;
1075     QVariant varian = item->property("load_mates");
1076     if(!varian.isValid()) //typically when a soup is oriented, the soup_item is deleted and thus the varain points to an unexisting item.
1077     {
1078       Scene_item* new_item = new_items.front();
1079       new_item->setName(item->name());
1080       new_item->setColor(item->color());
1081       new_item->setRenderingMode(item->renderingMode());
1082       new_item->setVisible(item->visible());
1083       Scene_item_with_properties *property_item = dynamic_cast<Scene_item_with_properties*>(new_item);
1084       scene->replaceItem(scene->item_id(item), new_item, true);
1085       if(property_item)
1086         property_item->copyProperties(item);
1087       new_item->invalidateOpenGLBuffers();
1088       item->deleteLater();
1089       return;
1090     }
1091     QSequentialIterable iterable = varian.value<QSequentialIterable>();
1092 
1093        // Can use foreach:
1094     int mate_id = 0;
1095     Q_FOREACH(const QVariant &v, iterable)
1096     {
1097       Scene_item* mate = v.value<Scene_item*>();
1098       Scene_item* new_item = new_items[mate_id];
1099       new_item->setName(mate->name());
1100       new_item->setColor(mate->color());
1101       new_item->setRenderingMode(mate->renderingMode());
1102       new_item->setVisible(mate->visible());
1103       Scene_item_with_properties *property_item = dynamic_cast<Scene_item_with_properties*>(new_item);
1104       scene->replaceItem(scene->item_id(mate), new_item, true);
1105       if(property_item)
1106         property_item->copyProperties(mate);
1107       new_item->invalidateOpenGLBuffers();
1108       mate->deleteLater();
1109     }
1110   }
1111 }
1112 
findLoader(const QString & loader_name) const1113 CGAL::Three::Polyhedron_demo_io_plugin_interface* MainWindow::findLoader(const QString& loader_name) const {
1114   Q_FOREACH(CGAL::Three::Polyhedron_demo_io_plugin_interface* io_plugin,
1115             io_plugins) {
1116     if(io_plugin->name() == loader_name) {
1117       return io_plugin;
1118     }
1119   }
1120   throw std::invalid_argument(QString("No loader found with the name %1 available")
1121                               .arg(loader_name).toStdString()) ;
1122 }
1123 
file_matches_filter(const QString & filters,const QString & filename)1124 bool MainWindow::file_matches_filter(const QString& filters,
1125                                      const QString& filename )
1126 {
1127   QFileInfo fileinfo(filename);
1128   QString filename_striped=fileinfo.fileName();
1129 
1130   //match all filters between ()
1131   QRegExp all_filters_rx("\\((.*)\\)");
1132 
1133   QStringList split_filters = filters.split(";;");
1134   Q_FOREACH(const QString& filter, split_filters) {
1135     //extract filters
1136     if ( all_filters_rx.indexIn(filter)!=-1 ){
1137       Q_FOREACH(const QString& pattern,all_filters_rx.cap(1).split(' ')){
1138         QRegExp rx(pattern);
1139         rx.setPatternSyntax(QRegExp::Wildcard);
1140         if ( rx.exactMatch(filename_striped) ){
1141           return true;
1142         }
1143       }
1144     }
1145   }
1146   return false;
1147 }
1148 
open(QString filename)1149 void MainWindow::open(QString filename)
1150 {
1151   QFileInfo fileinfo(filename);
1152 
1153 #ifdef QT_SCRIPT_LIB
1154   // Handles the loading of script file from the command line arguments,
1155   // and the special command line arguments that start with "javascript:"
1156   // or "qtscript:"
1157   QString program;
1158   if(filename.startsWith("javascript:")) {
1159     program=filename.right(filename.size() - 11);
1160   }
1161   if(filename.startsWith("qtscript:")) {
1162     program=filename.right(filename.size() - 9);
1163   }
1164   if(filename.endsWith(".js")) {
1165     loadScript(fileinfo);
1166     return;
1167   }
1168   if(!program.isEmpty())
1169   {
1170     {
1171       QTextStream(stderr) << "Execution of script \""
1172                           << filename << "\"\n";
1173       // << filename << "\", with following content:\n"
1174       // << program;
1175     }
1176     QApplication::setOverrideCursor(Qt::WaitCursor);
1177     evaluate_script(program, filename);
1178     QApplication::restoreOverrideCursor();
1179     return;
1180   }
1181 #endif
1182 
1183   if ( !fileinfo.exists() ){
1184     QMessageBox::warning(this,
1185                          tr("Cannot open file"),
1186                          tr("File %1 does not exist.")
1187                          .arg(filename));
1188     return;
1189   }
1190 
1191 
1192   QStringList selected_items;
1193   QStringList all_items;
1194 
1195   QMap<QString,QString>::iterator dfs_it =
1196       default_plugin_selection.find( fileinfo.completeSuffix() );
1197 
1198   if ( dfs_it==default_plugin_selection.end() )
1199   {
1200     // collect all io_plugins and offer them to load if the file extension match one name filter
1201     // also collect all available plugin in case of a no extension match
1202     for(CGAL::Three::Polyhedron_demo_io_plugin_interface* io_plugin : io_plugins) {
1203       if ( file_matches_filter(io_plugin->loadNameFilters(), filename.toLower()) )
1204       {
1205         if ( !io_plugin->canLoad(fileinfo) ) continue;
1206         all_items << io_plugin->name();
1207         if(io_plugin->isDefaultLoader(fileinfo.completeSuffix()))
1208           selected_items.prepend(io_plugin->name());
1209         else
1210           selected_items << io_plugin->name();
1211       }
1212     }
1213     //if no plugin is correct, offer them all.
1214     for(CGAL::Three::Polyhedron_demo_io_plugin_interface* io_plugin : io_plugins) {
1215         all_items << io_plugin->name();
1216     }
1217   }
1218   else
1219     selected_items << *dfs_it;
1220 
1221   bool ok;
1222   std::pair<QString, bool> load_pair;
1223 
1224   switch( selected_items.size() )
1225   {
1226   case 1:
1227     load_pair = std::make_pair(selected_items.first(), false);
1228     ok=true;
1229     break;
1230   case 0:
1231     load_pair = File_loader_dialog::getItem(fileinfo.fileName(), all_items, &ok);
1232     break;
1233   default:
1234     load_pair = File_loader_dialog::getItem(fileinfo.fileName(), selected_items, &ok);
1235   }
1236   //viewer->makeCurrent();
1237   if(!ok || load_pair.first.isEmpty()) { return; }
1238 
1239   if (load_pair.second)
1240   {
1241     connect(actionResetDefaultLoaders, SIGNAL(triggered()),
1242             this, SLOT(reset_default_loaders()));
1243     default_plugin_selection[fileinfo.completeSuffix()]=load_pair.first;
1244     insertActionBeforeLoadPlugin(ui->menuFile, actionResetDefaultLoaders);
1245   }
1246 
1247 
1248   settings.setValue("OFF open directory",
1249                     fileinfo.absoluteDir().absolutePath());
1250   loadItem(fileinfo, findLoader(load_pair.first), ok);
1251 
1252   if(!ok)
1253     return;
1254   this->addToRecentFiles(fileinfo.absoluteFilePath());
1255   updateViewersBboxes(true);
1256 }
1257 
open(QString filename,QString loader_name)1258 bool MainWindow::open(QString filename, QString loader_name) {
1259   QFileInfo fileinfo(filename);
1260   boost::optional<bool> item_opt;
1261   try {
1262     item_opt = wrap_a_call_to_cpp
1263         ([this, fileinfo, loader_name]()
1264     {
1265       bool ok;
1266       loadItem(fileinfo, findLoader(loader_name), ok);
1267       return ok;
1268     },
1269     this, __FILE__, __LINE__
1270     );
1271     if(!item_opt) return false;
1272   }
1273   catch(std::logic_error& e) {
1274     std::cerr << e.what() << std::endl;
1275     return false;
1276   }
1277   return true;
1278 }
1279 
1280 
loadItem(QFileInfo fileinfo,CGAL::Three::Polyhedron_demo_io_plugin_interface * loader,bool & ok,bool add_to_scene)1281 QList<Scene_item*> MainWindow::loadItem(QFileInfo fileinfo,
1282                                         CGAL::Three::Polyhedron_demo_io_plugin_interface* loader,
1283                                         bool &ok,
1284                                         bool add_to_scene) {
1285   if(!fileinfo.isFile() || !fileinfo.isReadable()) {
1286     QMessageBox::warning(this, tr("Error"),
1287                          QString("File %1 is not a readable file.")
1288                          .arg(fileinfo.absoluteFilePath()));
1289   }
1290   QCursor tmp_cursor(Qt::WaitCursor);
1291   CGAL::Three::Three::CursorScopeGuard guard(tmp_cursor);
1292   QList<Scene_item*> result = loader->load(fileinfo, ok, add_to_scene);
1293   if(!ok)
1294   {
1295     QApplication::restoreOverrideCursor();
1296       QMessageBox::warning(this, tr("Error"),
1297                            QString("Could not load item from file %1 using plugin %2")
1298                                                       .arg(fileinfo.absoluteFilePath()).arg(loader->name()));
1299       return QList<Scene_item*>();
1300   }
1301   selectSceneItem(scene->item_id(result.back()));
1302   for(Scene_item* item : result)
1303   {
1304     CGAL::Three::Scene_group_item* group =
1305         qobject_cast<CGAL::Three::Scene_group_item*>(item);
1306     if(group)
1307       scene->redraw_model();
1308     item->setProperty("source filename", fileinfo.absoluteFilePath());
1309     item->setProperty("loader_name", loader->name());
1310     item->setProperty("load_mates",QVariant::fromValue(result));
1311   }
1312   return result;
1313 }
1314 
1315 
setFocusToQuickSearch()1316 void MainWindow::setFocusToQuickSearch()
1317 {
1318   ui->searchEdit->setFocus(Qt::ShortcutFocusReason);
1319 }
1320 
selectSceneItem(int i)1321 void MainWindow::selectSceneItem(int i)
1322 {
1323   if(i < 0 || i >= scene->numberOfEntries()) {
1324     sceneView->selectionModel()->clearSelection();
1325     updateInfo();
1326     updateDisplayInfo();
1327   }
1328   else {
1329     QItemSelection s =
1330         proxyModel->mapSelectionFromSource(scene->createSelection(i));
1331     if(s.empty())
1332       return;
1333     QModelIndex mi = proxyModel->mapFromSource(scene->getModelIndexFromId(i).first());
1334     sceneView->setCurrentIndex(mi);
1335     sceneView->selectionModel()->select(s,
1336                                         QItemSelectionModel::ClearAndSelect);
1337     sceneView->scrollTo(s.indexes().first());
1338     sceneView->setCurrentIndex(sceneView->selectionModel()->selectedIndexes().first());
1339   }
1340 }
1341 
selectSceneItems(QList<int> is)1342 void MainWindow::selectSceneItems(QList<int> is)
1343 {
1344   if(is.first() < 0 || is.last() >= scene->numberOfEntries()) {
1345     sceneView->selectionModel()->clearSelection();
1346     updateInfo();
1347     updateDisplayInfo();
1348   }
1349   else {
1350     QItemSelection s =
1351       proxyModel->mapSelectionFromSource(scene->createSelection(is));
1352 
1353     QModelIndex i = proxyModel->mapFromSource(scene->getModelIndexFromId(is.first()).first());
1354     sceneView->setCurrentIndex(i);
1355     sceneView->selectionModel()->select(s,
1356                                         QItemSelectionModel::ClearAndSelect);
1357     if(!s.empty())
1358       sceneView->scrollTo(s.indexes().first());
1359   }
1360 }
1361 
1362 
showSelectedPoint(double x,double y,double z)1363 void MainWindow::showSelectedPoint(double x, double y, double z)
1364 {
1365   static double x_prev = 0;
1366   static double y_prev = 0;
1367   static double z_prev = 0;
1368   double dist = std::sqrt((x-x_prev)*(x-x_prev) + (y-y_prev)*(y-y_prev) + (z-z_prev)*(z-z_prev));
1369   information(QString("Selected point: (%1, %2, %3) distance to previous: %4").
1370               arg(x, 0, 'g', 10).
1371               arg(y, 0, 'g', 10).
1372               arg(z, 0, 'g', 10).
1373               arg(dist,0,'g',10));
1374   x_prev = x;
1375   y_prev = y;
1376   z_prev = z;
1377 }
1378 
unSelectSceneItem(int i)1379 void MainWindow::unSelectSceneItem(int i)
1380 {
1381   removeSceneItemFromSelection(i);
1382 }
1383 
addSceneItemInSelection(int i)1384 void MainWindow::addSceneItemInSelection(int i)
1385 {
1386   QItemSelection s =
1387       proxyModel->mapSelectionFromSource(scene->createSelection(i));
1388   sceneView->selectionModel()->select(s, QItemSelectionModel::Select);
1389   scene->itemChanged(i);
1390 }
1391 
removeSceneItemFromSelection(int i)1392 void MainWindow::removeSceneItemFromSelection(int i)
1393 {
1394   QItemSelection s =
1395       proxyModel->mapSelectionFromSource(scene->createSelection(i));
1396   sceneView->selectionModel()->select(s,
1397                                       QItemSelectionModel::Deselect);
1398   scene->itemChanged(i);
1399 }
1400 
selectAll()1401 void MainWindow::selectAll()
1402 {
1403   sceneView->selectAll();
1404 }
1405 
getSelectedSceneItemIndex() const1406 int MainWindow::getSelectedSceneItemIndex() const
1407 {
1408   QModelIndexList selectedRows = sceneView->selectionModel()->selectedIndexes();
1409   if(selectedRows.size() == 0)
1410     return -1;
1411   else {
1412     QModelIndex i = proxyModel->mapToSource(selectedRows.first());
1413     return scene->getIdFromModelIndex(i);
1414   }
1415 }
1416 
getSelectedSceneItemIndices() const1417 QList<int> MainWindow::getSelectedSceneItemIndices() const
1418 {
1419   QModelIndexList selectedIndices = sceneView->selectionModel()->selectedIndexes();
1420   QList<int> result;
1421   Q_FOREACH(QModelIndex index, selectedIndices) {
1422     int temp = scene->getIdFromModelIndex(proxyModel->mapToSource(index));
1423     if(!result.contains(temp))
1424       result<<temp;
1425   }
1426   return result;
1427 }
1428 
selectionChanged()1429 void MainWindow::selectionChanged()
1430 {
1431   scene->setSelectedItemIndex(getSelectedSceneItemIndex());
1432   scene->setSelectedItemsList(getSelectedSceneItemIndices());
1433   CGAL::Three::Scene_item* item = scene->item(getSelectedSceneItemIndex());
1434   Q_FOREACH(CGAL::QGLViewer* vi, CGAL::QGLViewer::QGLViewerPool())
1435   {
1436     if(vi == nullptr)
1437       continue;
1438 
1439     if(item != nullptr && item->manipulatable()) {
1440       vi->setManipulatedFrame(item->manipulatedFrame());
1441     } else {
1442       vi->setManipulatedFrame(nullptr);
1443     }
1444     if(vi->manipulatedFrame() == nullptr) {
1445       Q_FOREACH(CGAL::Three::Scene_item* item, scene->entries()) {
1446         if(item->manipulatable() && item->manipulatedFrame() != nullptr) {
1447           if(vi->manipulatedFrame() != nullptr) {
1448             // there are at least two possible frames
1449             vi->setManipulatedFrame(nullptr);
1450             break;
1451           } else {
1452             vi->setManipulatedFrame(item->manipulatedFrame());
1453           }
1454         }
1455       }
1456     }
1457     if(vi->manipulatedFrame() != nullptr) {
1458       connect(vi->manipulatedFrame(), SIGNAL(modified()),
1459               this, SLOT(updateInfo()));
1460     }
1461     vi->update();
1462   }
1463 }
contextMenuRequested(const QPoint & global_pos)1464 void MainWindow::contextMenuRequested(const QPoint& global_pos) {
1465   int index = scene->mainSelectionIndex();
1466   showSceneContextMenu(index, global_pos);
1467 }
1468 
showSceneContextMenu(int selectedItemIndex,const QPoint & global_pos)1469 void MainWindow::showSceneContextMenu(int selectedItemIndex,
1470                                       const QPoint& global_pos)
1471 {
1472   CGAL::Three::Scene_item* item = scene->item(selectedItemIndex);
1473   if(!item) return;
1474 
1475   const char* prop_name = "Menu modified by MainWindow.";
1476 
1477   QMenu* menu = item->contextMenu();
1478   if(menu) {
1479     bool menuChanged = menu->property(prop_name).toBool();
1480     if(!menuChanged) {
1481       if(item->has_stats())
1482       {
1483         QAction* actionStatistics =
1484             menu->addAction(tr("Statistics..."));
1485         actionStatistics->setObjectName("actionStatisticsOnPolyhedron");
1486         connect(actionStatistics, SIGNAL(triggered()),
1487                 this, SLOT(statisticsOnItem()));
1488       }
1489       menu->addSeparator();
1490       if(!item->property("source filename").toString().isEmpty()) {
1491         QAction* reload = menu->addAction(tr("&Reload Item from File"));
1492         reload->setProperty("is_groupable", true);
1493         connect(reload, SIGNAL(triggered()),
1494                 this, SLOT(reloadItem()));
1495       }
1496       QAction* saveas = menu->addAction(tr("&Save as..."));
1497       saveas->setData(QVariant::fromValue((void*)item));
1498       connect(saveas,  SIGNAL(triggered()),
1499               this, SLOT(on_actionSaveAs_triggered()));
1500       QAction* showobject = menu->addAction(tr("&Zoom to this Object"));
1501       showobject->setData(QVariant::fromValue((void*)item));
1502       connect(showobject, SIGNAL(triggered()),
1503               this, SLOT(viewerShowObject()));
1504 
1505       menu->setProperty(prop_name, true);
1506     }
1507   }
1508   menu->addMenu(ui->menuOperations);
1509 
1510   if(menu)
1511     menu->exec(global_pos);
1512 }
1513 
showSceneContextMenu(const QPoint & p)1514 void MainWindow::showSceneContextMenu(const QPoint& p) {
1515   QWidget* sender = qobject_cast<QWidget*>(this->sender());
1516   if(!sender) return;
1517   if(scene->selectionIndices().isEmpty())return;
1518   int main_index = scene->selectionIndices().first();
1519 
1520   if(sender == sceneView) {
1521       QModelIndex modelIndex = sceneView->indexAt(p);
1522       if(!modelIndex.isValid())
1523       {
1524           const char* prop_name = "Menu modified by MainWindow.";
1525 
1526           QMenu* menu = ui->menuFile;
1527           if(menu) {
1528               bool menuChanged = menu->property(prop_name).toBool();
1529               if(!menuChanged) {
1530                   menu->setProperty(prop_name, true);
1531               }
1532           }
1533           if(menu)
1534               menu->exec(sender->mapToGlobal(p));
1535           return;
1536       }
1537       else if(scene->selectionIndices().size() > 1 )
1538       {
1539         QMap<QString, QAction*> menu_actions;
1540         QVector<QMenu*> slider_menus;
1541         bool has_stats = false;
1542         bool has_reload = false;
1543         Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
1544         {
1545           if(!scene->item(id)->property("source filename").toString().isEmpty())
1546           {
1547             has_reload = true;
1548             break;
1549           }
1550         }
1551         Q_FOREACH(QAction* action, scene->item(main_index)->contextMenu()->actions())
1552         {
1553           if(action->property("is_groupable").toBool())
1554           {
1555             menu_actions[action->text()] = action;
1556             if(action->text() == QString("Alpha value"))
1557             {
1558               menu_actions["alpha slider"] = action->menu()->actions().last();
1559             }
1560             else if(action->text() == QString("Points Size"))
1561             {
1562               menu_actions["points slider"] = action->menu()->actions().last();
1563             }
1564             else if(action->text() == QString("Normals Length"))
1565             {
1566               menu_actions["normals slider"] = action->menu()->actions().last();
1567             }
1568             else if(action->text() == QString("Line Width"))
1569             {
1570               menu_actions["Line width"] = action->menu()->actions().last();
1571             }
1572           }
1573 
1574         }
1575         Q_FOREACH(Scene::Item_id index, scene->selectionIndices())
1576         {
1577           if(index == main_index)
1578             continue;
1579 
1580           CGAL::Three::Scene_item* item = scene->item(index);
1581           if(!item)
1582             continue;
1583           if(item->has_stats())
1584             has_stats = true;
1585         }
1586         QMenu menu;
1587         menu.addAction(actionAddToGroup);
1588         menu.insertSeparator(nullptr);
1589         Q_FOREACH(QString name, menu_actions.keys())
1590         {
1591           if(name == QString("alpha slider")
1592              || name == QString("points slider")
1593              || name == QString("normals slider"))
1594             continue;
1595           if(name == QString("Alpha value"))
1596           {
1597             QWidgetAction* sliderAction = new QWidgetAction(&menu);
1598             QSlider* slider = new QSlider(&menu);
1599             slider->setMinimum(0);
1600             slider->setMaximum(255);
1601             slider->setValue(
1602                   qobject_cast<QSlider*>(
1603                     qobject_cast<QWidgetAction*>
1604                     (menu_actions["alpha slider"])->defaultWidget()
1605                   )->value());
1606             slider->setOrientation(Qt::Horizontal);
1607             sliderAction->setDefaultWidget(slider);
1608 
1609             connect(slider, &QSlider::valueChanged, [this, slider]()
1610             {
1611               Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
1612               {
1613                 Scene_item* item = scene->item(id);
1614                 Q_FOREACH(QAction* action, item->contextMenu()->actions())
1615                 {
1616                   if(action->text() == "Alpha value")
1617                   {
1618                     QWidgetAction* sliderAction = qobject_cast<QWidgetAction*>(action->menu()->actions().last());
1619                     QSlider* ac_slider = qobject_cast<QSlider*>(sliderAction->defaultWidget());
1620                     ac_slider->setValue(slider->value());
1621                     break;
1622                   }
1623                 }
1624               }
1625             });
1626             QMenu* new_menu = new QMenu("Alpha value", &menu);
1627               new_menu->addAction(sliderAction);
1628               slider_menus.push_back(new_menu);
1629           }
1630           else if(name == QString("Points Size"))
1631           {
1632             QWidgetAction* sliderAction = new QWidgetAction(&menu);
1633             QSlider* slider = new QSlider(&menu);
1634             slider->setMinimum(1);
1635             slider->setMaximum(25);
1636             slider->setValue(
1637                   qobject_cast<QSlider*>(
1638                     qobject_cast<QWidgetAction*>
1639                     (menu_actions["points slider"])->defaultWidget()
1640                   )->value());
1641             slider->setOrientation(Qt::Horizontal);
1642             sliderAction->setDefaultWidget(slider);
1643 
1644             connect(slider, &QSlider::valueChanged, [this, slider]()
1645             {
1646               Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
1647               {
1648                 Scene_item* item = scene->item(id);
1649                 Q_FOREACH(QAction* action, item->contextMenu()->actions())
1650                 {
1651                   if(action->text() == "Points Size")
1652                   {
1653                     QWidgetAction* sliderAction = qobject_cast<QWidgetAction*>(action->menu()->actions().last());
1654                     QSlider* ac_slider = qobject_cast<QSlider*>(sliderAction->defaultWidget());
1655                     ac_slider->setValue(slider->value());
1656                     break;
1657                   }
1658                 }
1659               }
1660             });
1661             QMenu* new_menu = new QMenu("Points Size", &menu);
1662               new_menu->addAction(sliderAction);
1663               slider_menus.push_back(new_menu);
1664           }
1665           else if(name == QString("Normals Length"))
1666           {
1667             QWidgetAction* sliderAction = new QWidgetAction(&menu);
1668             QSlider* slider = new QSlider(&menu);
1669             slider->setMinimum(0);
1670             slider->setMaximum(100);
1671             slider->setValue(
1672                   qobject_cast<QSlider*>(
1673                     qobject_cast<QWidgetAction*>
1674                     (menu_actions["normals slider"])->defaultWidget()
1675                   )->value());
1676             slider->setOrientation(Qt::Horizontal);
1677             sliderAction->setDefaultWidget(slider);
1678 
1679             connect(slider, &QSlider::valueChanged, [this, slider]()
1680             {
1681               Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
1682               {
1683                 Scene_item* item = scene->item(id);
1684                 Q_FOREACH(QAction* action, item->contextMenu()->actions())
1685                 {
1686                   if(action->text() == "Normals Length")
1687                   {
1688                     QWidgetAction* sliderAction = qobject_cast<QWidgetAction*>(action->menu()->actions().last());
1689                     QSlider* ac_slider = qobject_cast<QSlider*>(sliderAction->defaultWidget());
1690                     ac_slider->setValue(slider->value());
1691                     break;
1692                   }
1693                 }
1694               }
1695             });
1696             QMenu* new_menu = new QMenu("Normals Length", &menu);
1697               new_menu->addAction(sliderAction);
1698               slider_menus.push_back(new_menu);
1699           }
1700           else if(name == QString("Line Width"))
1701           {
1702             QWidgetAction* sliderAction = new QWidgetAction(&menu);
1703             QSlider* slider = new QSlider(&menu);
1704             slider->setMinimum(1);
1705             float lineWidth[2];
1706             if(!viewer->isOpenGL_4_3())
1707               viewer->glGetFloatv(GL_LINE_WIDTH_RANGE, lineWidth);
1708             else
1709             {
1710               lineWidth[0] = 0;
1711               lineWidth[1] = 10;
1712             }
1713             slider->setMaximum(lineWidth[1]);
1714             slider->setValue(
1715                   qobject_cast<QSlider*>(
1716                     qobject_cast<QWidgetAction*>
1717                     (menu_actions["Line width"])->defaultWidget()
1718                   )->value());
1719             slider->setOrientation(Qt::Horizontal);
1720             sliderAction->setDefaultWidget(slider);
1721 
1722             connect(slider, &QSlider::valueChanged, [this, slider]()
1723             {
1724               Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
1725               {
1726                 Scene_item* item = scene->item(id);
1727                 Q_FOREACH(QAction* action, item->contextMenu()->actions())
1728                 {
1729                   if(action->text() == "Line Width")
1730                   {
1731                     QWidgetAction* sliderAction = qobject_cast<QWidgetAction*>(action->menu()->actions().last());
1732                     QSlider* ac_slider = qobject_cast<QSlider*>(sliderAction->defaultWidget());
1733                     ac_slider->setValue(slider->value());
1734                     break;
1735                   }
1736                 }
1737               }
1738             });
1739             QMenu* new_menu = new QMenu("Line Width", &menu);
1740               new_menu->addAction(sliderAction);
1741               slider_menus.push_back(new_menu);
1742           }
1743           else
1744           {
1745             QAction* action = menu.addAction(name);
1746             connect(action, &QAction::triggered, this, &MainWindow::propagate_action);
1747           }
1748         }
1749         if(!slider_menus.empty())
1750         {
1751           Q_FOREACH(QMenu* m, slider_menus){
1752             menu.addMenu(m);
1753           }
1754           menu.insertSeparator(nullptr);
1755         }
1756         if(has_stats)
1757         {
1758           QAction* actionStatistics =
1759               menu.addAction(tr("Statistics..."));
1760           actionStatistics->setObjectName("actionStatisticsOnPolyhedron");
1761           connect(actionStatistics, SIGNAL(triggered()),
1762                   this, SLOT(statisticsOnItem()));
1763         }
1764         if(has_reload)
1765         {
1766           QAction* reload = menu.addAction(tr("&Reload Item from File"));
1767           reload->setProperty("is_groupable", true);
1768           connect(reload, SIGNAL(triggered()),
1769                   this, SLOT(reloadItem()));
1770         }
1771         QAction* saveas = menu.addAction(tr("&Save as..."));
1772         connect(saveas,  SIGNAL(triggered()),
1773                 this, SLOT(on_actionSaveAs_triggered()));
1774         menu.addMenu(ui->menuOperations);
1775         menu.exec(sender->mapToGlobal(p));
1776         return;
1777       }
1778   }
1779   showSceneContextMenu(main_index, sender->mapToGlobal(p));
1780   return;
1781 }
1782 
removeManipulatedFrame(CGAL::Three::Scene_item * item)1783 void MainWindow::removeManipulatedFrame(CGAL::Three::Scene_item* item)
1784 {
1785   if(item->manipulatable() &&
1786      item->manipulatedFrame() == viewer->manipulatedFrame()) {
1787     viewer->setManipulatedFrame(nullptr);
1788   }
1789 }
1790 
updateInfo()1791 void MainWindow::updateInfo() {
1792   CGAL::Three::Scene_item* item = scene->item(getSelectedSceneItemIndex());
1793   if(item) {
1794     QString item_text = item->toolTip();
1795     QString item_filename = item->property("source filename").toString();
1796     CGAL::Bbox_3 bbox = item->bbox();
1797     if(bbox !=CGAL::Bbox_3())
1798       item_text += QString("<div>Bounding box: min (%1,%2,%3), max (%4,%5,%6), dimensions (%7, %8, %9)</div>")
1799           .arg(bbox.xmin(),0, 'g', 17)
1800           .arg(bbox.ymin(),0, 'g', 17)
1801           .arg(bbox.zmin(),0, 'g', 17)
1802           .arg(bbox.xmax(),0, 'g', 17)
1803           .arg(bbox.ymax(),0, 'g', 17)
1804           .arg(bbox.zmax(),0, 'g', 17)
1805           .arg(bbox.xmax() - bbox.xmin(), 0, 'g', 17)
1806           .arg(bbox.ymax() - bbox.ymin(), 0, 'g', 17)
1807           .arg(bbox.zmax() - bbox.zmin(), 0, 'g', 17);
1808     if(!item_filename.isEmpty()) {
1809       item_text += QString("<div>File:<i> %1</div>").arg(item_filename);
1810     }
1811     ui->infoLabel->setText(item_text);
1812   }
1813   else
1814     ui->infoLabel->clear();
1815 }
updateDisplayInfo()1816 void MainWindow::updateDisplayInfo() {
1817   CGAL::Three::Scene_item* item = scene->item(getSelectedSceneItemIndex());
1818   if(item)
1819     ui->displayLabel->setPixmap(item->graphicalToolTip());
1820   else
1821     ui->displayLabel->clear();
1822 
1823 }
1824 
readSettings()1825 void MainWindow::readSettings()
1826 {
1827     viewer->setAntiAliasing(settings.value("antialiasing", false).toBool());
1828     viewer->setFastDrawing(settings.value("quick_camera_mode", true).toBool());
1829     scene->enableVisibilityRecentering(settings.value("offset_update", false).toBool());
1830     viewer->textRenderer()->setMax(settings.value("max_text_items", 10000).toInt());
1831     int val  = settings.value("transparency_pass_number", 4).toInt();
1832     if (val < 4 ) {
1833       val = 4;
1834       settings.setValue("transparency_pass_number", 4);
1835     }
1836     viewer->setTotalPass(val);
1837     CGAL::Three::Three::s_defaultSMRM = CGAL::Three::Three::modeFromName(
1838           settings.value("default_sm_rm", "flat+edges").toString());
1839     CGAL::Three::Three::s_defaultPSRM = CGAL::Three::Three::modeFromName(
1840           settings.value("default_ps_rm", "points").toString());
1841     // read plugin blacklist
1842     QStringList blacklist=settings.value("plugin_blacklist",QStringList()).toStringList();
1843     Q_FOREACH(QString name,blacklist){ plugin_blacklist.insert(name); }
1844     def_save_dir = settings.value("default_saveas_dir", QDir::homePath()).toString();
1845     this->default_point_size = settings.value("points_size").toInt();
1846     this->default_normal_length = settings.value("normals_length").toInt();
1847     this->default_lines_width = settings.value("lines_width").toInt();
1848     setProperty("ws_url", settings.value("ws_server_url").toString());
1849 }
1850 
writeSettings()1851 void MainWindow::writeSettings()
1852 {
1853   this->writeState("MainWindow");
1854   {
1855     //setting plugin blacklist
1856     QStringList blacklist;
1857     Q_FOREACH(QString name,plugin_blacklist){ blacklist << name; }
1858     if ( !blacklist.isEmpty() ) settings.setValue("plugin_blacklist",blacklist);
1859     else settings.remove("plugin_blacklist");
1860   }
1861   std::cerr << "Write setting... done.\n";
1862 }
1863 
quit()1864 void MainWindow::quit()
1865 {
1866   close();
1867 }
1868 
closeEvent(QCloseEvent * event)1869 void MainWindow::closeEvent(QCloseEvent *event)
1870 {
1871   for(int i=0; i<plugins.size(); i++)
1872   {
1873     plugins[i].first->closure();
1874   }
1875   writeSettings();
1876   event->accept();
1877 }
1878 
loadScript(QString filename)1879 bool MainWindow::loadScript(QString filename)
1880 {
1881   QFileInfo fileinfo(filename);
1882   boost::optional<bool> opt = wrap_a_call_to_cpp
1883       ([this, fileinfo] {
1884     return loadScript(fileinfo);
1885   }, this, __FILE__, __LINE__, CGAL::Three::PARENT_CONTEXT);
1886   if(!opt) return false;
1887   else return *opt;
1888 }
1889 
loadScript(QFileInfo info)1890 bool MainWindow::loadScript(QFileInfo info)
1891 {
1892 #if defined(QT_SCRIPT_LIB)
1893   QString program;
1894   QString filename = info.absoluteFilePath();
1895   QFile script_file(filename);
1896   script_file.open(QIODevice::ReadOnly);
1897   if(!script_file.isReadable()) {
1898     throw std::ios_base::failure(script_file.errorString().toStdString());
1899   }
1900   program = script_file.readAll();
1901   if(!program.isEmpty())
1902   {
1903     QTextStream(stderr)
1904         << "Execution of script \""
1905         << filename << "\"\n";
1906     evaluate_script(program, filename);
1907     return true;
1908   }
1909 #endif
1910   return false;
1911 }
1912 
throw_exception()1913 void MainWindow::throw_exception() {
1914   wrap_a_call_to_cpp([]() {
1915     throw std::runtime_error("Exception thrown in "
1916                              "MainWindow::throw_exception()");
1917   }, this, __FILE__, __LINE__);
1918 }
1919 
on_actionLoadScript_triggered()1920 void MainWindow::on_actionLoadScript_triggered()
1921 {
1922 #if defined(QT_SCRIPT_LIB)
1923 
1924 #endif
1925 }
1926 
on_actionLoad_triggered()1927 void MainWindow::on_actionLoad_triggered()
1928 {
1929   QStringList filters;
1930   // we need to special case our way out of this
1931   filters << "All Files (*)";
1932 
1933 
1934   typedef QMap<QString, CGAL::Three::Polyhedron_demo_io_plugin_interface*> FilterPluginMap;
1935   FilterPluginMap filterPluginMap;
1936 
1937   for(CGAL::Three::Polyhedron_demo_io_plugin_interface* plugin : io_plugins) {
1938     QStringList split_filters = plugin->loadNameFilters().split(";;");
1939     Q_FOREACH(const QString& filter, split_filters) {
1940       FilterPluginMap::iterator it = filterPluginMap.find(filter);
1941       if(it != filterPluginMap.end()) {
1942         if(verbose)
1943         {
1944           qDebug() << "Duplicate Filter: " << it.value()->name();
1945           qDebug() << "This filter will not be available.";
1946         }
1947       } else {
1948         filterPluginMap[filter] = plugin;
1949       }
1950       filters << filter;
1951     }
1952   }
1953 
1954   QString directory = settings.value("OFF open directory",
1955                                      QDir::current().dirName()).toString();
1956 
1957   QFileDialog dialog(this);
1958   dialog.setDirectory(directory);
1959   dialog.setNameFilters(filters);
1960   dialog.setFileMode(QFileDialog::ExistingFiles);
1961 
1962   if(dialog.exec() != QDialog::Accepted) { return; }
1963   for(auto v : CGAL::QGLViewer::QGLViewerPool())
1964     v->update();
1965   FilterPluginMap::iterator it =
1966     filterPluginMap.find(dialog.selectedNameFilter());
1967 
1968   CGAL::Three::Polyhedron_demo_io_plugin_interface* selectedPlugin = nullptr;
1969 
1970   if(it != filterPluginMap.end()) {
1971     selectedPlugin = it.value();
1972   }
1973 
1974   std::size_t nb_files = dialog.selectedFiles().size();
1975   std::vector<QColor> colors_;
1976   colors_.reserve(nb_files);
1977   compute_color_map(QColor(100, 100, 255),//Scene_item's default color
1978                     static_cast<unsigned>(nb_files),
1979                     std::back_inserter(colors_));
1980   std::size_t nb_item = -1;
1981 
1982   Q_FOREACH(const QString& filename, dialog.selectedFiles()) {
1983 
1984     CGAL::Three::Scene_item* item = nullptr;
1985     if(selectedPlugin) {
1986       QFileInfo info(filename);
1987       bool ok;
1988       QList<Scene_item*> result = loadItem(info, selectedPlugin, ok);
1989       if(!ok)
1990         continue;
1991       for(Scene_item* item : result)
1992       {
1993         if(!item->property("already_colored").toBool())
1994         {
1995           ++nb_item;
1996           item->setColor(colors_[nb_item]);
1997         }
1998         selectSceneItem(scene->item_id(item));
1999         CGAL::Three::Scene_group_item* group =
2000             qobject_cast<CGAL::Three::Scene_group_item*>(item);
2001         if(group)
2002           scene->redraw_model();
2003       }
2004       this->addToRecentFiles(filename);
2005     } else {
2006       int scene_size = scene->numberOfEntries();
2007       open(filename);
2008       item = scene->item(scene->numberOfEntries()-1);
2009       if(scene->numberOfEntries() != scene_size
2010          && !item->property("already_colored").toBool())
2011         item->setColor(colors_[++nb_item]);
2012     }
2013   }
2014 }
2015 
on_actionSaveAs_triggered()2016 void MainWindow::on_actionSaveAs_triggered()
2017 {
2018   QList<Scene_item*> to_save;
2019   for(Scene::Item_id id : scene->selectionIndices())
2020   {
2021     Scene_item* item = scene->item(id);
2022     to_save.append(item);
2023   }
2024   while(!to_save.empty())
2025   {
2026     Scene_item* item = to_save.front();
2027     QVector<CGAL::Three::Polyhedron_demo_io_plugin_interface*> canSavePlugins;
2028     QStringList filters;
2029     QString sf;
2030     for(CGAL::Three::Polyhedron_demo_io_plugin_interface* plugin : io_plugins) {
2031       if(plugin->canSave(item)) {
2032         canSavePlugins << plugin;
2033         filters += plugin->saveNameFilters();
2034         if(plugin->isDefaultLoader(item))
2035           sf = plugin->saveNameFilters().split(";;").first();
2036       }
2037     }
2038     QRegExp extensions("\\(\\*\\..+\\)");
2039     QStringList filter_exts;
2040     if(filters.empty())
2041     {
2042       QMessageBox::warning(this,
2043                            tr("Cannot save"),
2044                            tr("The selected object %1 cannot be saved.")
2045                            .arg(item->name()));
2046           return;
2047     }
2048     Q_FOREACH(QString string, filters)
2049     {
2050       QStringList sl = string.split(";;");
2051       Q_FOREACH(QString s, sl){
2052         int pos = extensions.indexIn(s);
2053         if( pos >-1)
2054           filter_exts.append(extensions.capturedTexts());
2055       }
2056     }
2057     filters << tr("All files (*)");
2058     if(canSavePlugins.isEmpty()) {
2059       QMessageBox::warning(this,
2060                            tr("Cannot save"),
2061                            tr("The selected object %1 cannot be saved.")
2062                            .arg(item->name()));
2063       continue;
2064     }
2065     QString caption = tr("Save %1 to File...").arg(item->name());
2066     QString dir = item->property("source filename").toString();
2067     if(dir.isEmpty() &&
2068        !item->property("defaultSaveDir").toString().isEmpty())
2069     {
2070       dir = item->property("defaultSaveDir").toString();
2071     }
2072     else if(!last_saved_dir.isEmpty() && dir.isEmpty())
2073       dir = QString("%1/%2").arg(last_saved_dir).arg(item->defaultSaveName());
2074     else if(dir.isEmpty())
2075       dir = QString("%1/%2").arg(def_save_dir).arg(item->name());
2076     QString filename =
2077         QFileDialog::getSaveFileName(this,
2078                                      caption,
2079                                      dir,
2080                                      filters.join(";;"),
2081                                      &sf);
2082 
2083     if(filename.isEmpty())
2084       return;
2085     last_saved_dir = QFileInfo(filename).absoluteDir().path();
2086     extensions.indexIn(sf.split(";;").first());
2087     QString filter_ext, filename_ext;
2088     filter_ext = extensions.cap().split(" ").first();// in case of syntax like (*.a *.b)
2089 
2090     filter_ext.remove(")");
2091     filter_ext.remove("(");
2092     //remove *
2093     filter_ext=filter_ext.right(filter_ext.size()-1);
2094 
2095     QStringList filename_split = filename.split(".");
2096     filename_split.removeFirst();
2097     filename_ext = filename_split.join(".");
2098     filename_ext.push_front(".");
2099 
2100     QStringList final_extensions;
2101     Q_FOREACH(QString string, filter_exts)
2102     {
2103       Q_FOREACH(QString s, string.split(" ")){// in case of syntax like (*.a *.b)
2104         s.remove(")");
2105         s.remove("(");
2106         //remove *
2107         s=s.right(s.size()-1);
2108         final_extensions.append(s);
2109       }
2110     }
2111     bool ok = false;
2112     while(!ok)
2113     {
2114       if(final_extensions.contains(filename_ext))
2115       {
2116         ok = true;
2117       }
2118       else{
2119         QStringList shatterd_filename_ext = filename_ext.split(".");
2120         if(!shatterd_filename_ext.last().isEmpty())
2121         {
2122           shatterd_filename_ext.removeFirst();//removes ""
2123           shatterd_filename_ext.removeFirst();
2124           filename_ext = shatterd_filename_ext.join(".");
2125           filename_ext.push_front(".");
2126         }
2127         else
2128           break;
2129       }
2130     }
2131     if(!ok)
2132     {
2133       filename = filename.append(filter_ext);
2134     }
2135     for(auto v : CGAL::QGLViewer::QGLViewerPool())
2136       v->update();
2137     save(filename, to_save);
2138   }
2139 }
2140 
save(QString filename,QList<CGAL::Three::Scene_item * > & to_save)2141 void MainWindow::save(QString filename, QList<CGAL::Three::Scene_item*>& to_save) {
2142   QFileInfo fileinfo(filename);
2143   bool saved = false;
2144   for(CGAL::Three::Polyhedron_demo_io_plugin_interface* plugin : io_plugins) {
2145     if(  plugin->canSave(to_save.front()) &&
2146          file_matches_filter(plugin->saveNameFilters(),filename.toLower()) )
2147     {
2148       if(plugin->save(fileinfo, to_save))
2149       {
2150         saved = true;
2151         break;
2152       }
2153     }
2154   }
2155   if(!saved)
2156   {
2157     QMessageBox::warning(this,
2158                          tr("Cannot save"),
2159                          tr("The selected object %1 was not saved. (Maybe a wrong extension ?)")
2160                          .arg(to_save.front()->name()));
2161     to_save.pop_front();
2162   }
2163 }
2164 
on_actionSaveSnapshot_triggered()2165 void MainWindow::on_actionSaveSnapshot_triggered()
2166 {
2167   viewer->saveSnapshot();
2168 }
2169 
on_actionErase_triggered()2170 bool MainWindow::on_actionErase_triggered()
2171 {
2172   int next_index = scene->erase(scene->selectionIndices());
2173   //Secure the case where erase triggers other items deletions
2174   if(scene->numberOfEntries()< next_index +1 )
2175     next_index = -1;
2176   selectSceneItem(next_index);
2177   return next_index >= 0;
2178 }
2179 
on_actionEraseAll_triggered()2180 void MainWindow::on_actionEraseAll_triggered()
2181 {
2182   QList<int> all_ids;
2183   for(int i = 0; i < scene->numberOfEntries(); ++i)
2184     all_ids.push_back(i);
2185   scene->setSelectedItemsList(all_ids);
2186   on_actionErase_triggered();
2187 }
2188 
on_actionDuplicate_triggered()2189 void MainWindow::on_actionDuplicate_triggered()
2190 {
2191   int index = scene->duplicate(getSelectedSceneItemIndex());
2192   selectSceneItem(index);
2193 }
2194 
on_actionShowHide_triggered()2195 void MainWindow::on_actionShowHide_triggered()
2196 {
2197   scene->setUpdatesEnabled(false);
2198   Q_FOREACH(QModelIndex index, sceneView->selectionModel()->selectedRows())
2199   {
2200     int i = scene->getIdFromModelIndex(proxyModel->mapToSource(index));
2201     CGAL::Three::Scene_item* item = scene->item(i);
2202     item->setVisible(!item->visible());
2203     item->redraw();
2204   }
2205   scene->setUpdatesEnabled(true);
2206 //  updateViewersBboxes(false); //Not usable :when the scene changes scale, smaller items disappear.
2207 }
2208 
on_actionSetPolyhedronA_triggered()2209 void MainWindow::on_actionSetPolyhedronA_triggered()
2210 {
2211   int i = getSelectedSceneItemIndex();
2212   scene->setItemA(i);
2213 }
2214 
on_actionSetPolyhedronB_triggered()2215 void MainWindow::on_actionSetPolyhedronB_triggered()
2216 {
2217   int i = getSelectedSceneItemIndex();
2218   scene->setItemB(i);
2219 }
2220 
on_actionPreferences_triggered()2221 void MainWindow::on_actionPreferences_triggered()
2222 {
2223   QDialog dialog(this);
2224   Ui::PreferencesDialog prefdiag;
2225   prefdiag.setupUi(&dialog);
2226   float lineWidth[2];
2227   if(!viewer->isOpenGL_4_3())
2228     viewer->glGetFloatv(GL_LINE_WIDTH_RANGE, lineWidth);
2229   else
2230   {
2231     lineWidth[0] = 0;
2232     lineWidth[1] = 10;
2233   }
2234   prefdiag.linesHorizontalSlider->setMinimum(lineWidth[0]);
2235   prefdiag.linesHorizontalSlider->setMaximum(lineWidth[1]);
2236 
2237   prefdiag.offset_updateCheckBox->setChecked(
2238         settings.value("offset_update", false).toBool());
2239   connect(prefdiag.offset_updateCheckBox, SIGNAL(toggled(bool)),
2240           scene, SLOT(enableVisibilityRecentering(bool)));
2241 
2242   prefdiag.antialiasingCheckBox->setChecked(settings.value("antialiasing", false).toBool());
2243   connect(prefdiag.antialiasingCheckBox, SIGNAL(toggled(bool)),
2244           viewer, SLOT(setAntiAliasing(bool)));
2245 
2246   prefdiag.quick_cameraCheckBox->setChecked(
2247         settings.value("quick_camera_mode", true).toBool());
2248   connect(prefdiag.quick_cameraCheckBox, SIGNAL(toggled(bool)),
2249           viewer, SLOT(setFastDrawing(bool)));
2250   prefdiag.max_itemsSpinBox->setValue(viewer->textRenderer()->getMax_textItems());
2251 
2252   connect(prefdiag.max_itemsSpinBox,static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
2253           this, [this](int i){
2254     setMaxTextItemsDisplayed(i);
2255   });
2256   prefdiag.transpSpinBox->setValue(viewer->total_pass());
2257   connect(prefdiag.transpSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
2258               this, [this](int i)
2259   {
2260     setTransparencyPasses(i);
2261   });
2262   prefdiag.pointsHorizontalSlider->setValue(this->default_point_size);
2263   connect(prefdiag.pointsHorizontalSlider, &QSlider::valueChanged,
2264               this, [this](int i)
2265   {
2266     this->default_point_size = i;
2267   });
2268   prefdiag.normalsHorizontalSlider->setValue(this->default_normal_length);
2269   connect(prefdiag.normalsHorizontalSlider, &QSlider::valueChanged,
2270               this, [this](int i)
2271   {
2272     this->default_normal_length = i;
2273   });
2274   prefdiag.linesHorizontalSlider->setValue(this->default_lines_width);
2275   connect(prefdiag.linesHorizontalSlider, &QSlider::valueChanged,
2276               this, [this](int i)
2277   {
2278     this->default_lines_width = i;
2279   });
2280   connect(prefdiag.background_colorPushButton, &QPushButton::clicked,
2281           this, &MainWindow::setBackgroundColor);
2282 
2283   connect(prefdiag.default_save_asPushButton, &QPushButton::clicked,
2284           this, &MainWindow::setDefaultSaveDir);
2285 
2286   connect(prefdiag.lightingPushButton, &QPushButton::clicked,
2287           this, &MainWindow::setLighting_triggered);
2288 
2289   prefdiag.surface_meshComboBox->setCurrentText(CGAL::Three::Three::modeName(
2290                                                   CGAL::Three::Three::s_defaultSMRM));
2291   connect(prefdiag.surface_meshComboBox, &QComboBox::currentTextChanged,
2292           this, [this](const QString& text){
2293     this->s_defaultSMRM = CGAL::Three::Three::modeFromName(text);
2294   });
2295 
2296   prefdiag.point_setComboBox->setCurrentText(CGAL::Three::Three::modeName(
2297                                                CGAL::Three::Three::s_defaultPSRM));
2298   connect(prefdiag.point_setComboBox, &QComboBox::currentTextChanged,
2299           this, [this](const QString& text){
2300     this->s_defaultPSRM = CGAL::Three::Three::modeFromName(text);
2301   });
2302 
2303   connect(prefdiag.backFrontColor_pushButton, &QPushButton::clicked,
2304           this, [](){
2305     qobject_cast<Viewer*>(CGAL::Three::Three::activeViewer())->setBackFrontColors();
2306   });
2307 
2308   std::vector<QTreeWidgetItem*> items;
2309   QBrush successBrush(Qt::green),
2310       errorBrush(Qt::red),
2311       ignoredBrush(Qt::lightGray);
2312 
2313   //add blacklisted plugins
2314   Q_FOREACH (QString name, PathNames_map.keys())
2315   {
2316     QTreeWidgetItem *item = new QTreeWidgetItem(prefdiag.treeWidget);
2317     item->setText(1, name);
2318     if(plugin_blacklist.contains(name)){
2319       item->setCheckState(0, Qt::Unchecked);
2320     }
2321     else{
2322       item->setCheckState(0, Qt::Checked);
2323     }
2324     if(pluginsStatus_map[name] == QString("success"))
2325       item->setBackground(1, successBrush);
2326     else if(ignored_map[name]){
2327       item->setBackground(1, ignoredBrush);
2328     }
2329     else{
2330       item->setBackground(1, errorBrush);
2331     }
2332     items.push_back(item);
2333   }
2334   connect(prefdiag.detailsPushButton, &QPushButton::clicked,
2335           this, [this, prefdiag](){
2336     QStringList titles;
2337     titles << "Name" << "Keywords" << "ConfigDate";
2338     QDialog dialog(this);
2339     Ui::DetailsDialog detdiag;
2340     detdiag.setupUi(&dialog);
2341     QTreeWidgetItem *header = new QTreeWidgetItem(titles);
2342     detdiag.treeWidget->setHeaderItem(header);
2343     Q_FOREACH(QTreeWidgetItem* plugin_item, prefdiag.treeWidget->selectedItems())
2344     {
2345       QString name = plugin_item->text(1);
2346       QString keywords = plugin_metadata_map[name].first.join(", ");
2347       QString date = plugin_metadata_map[name].second;
2348       QStringList values;
2349       values << name << keywords << date;
2350       new QTreeWidgetItem(detdiag.treeWidget, values);
2351     }
2352     for(int i=0; i<3; ++i)
2353     {
2354       detdiag.treeWidget->resizeColumnToContents(i);
2355     }
2356     connect(detdiag.treeWidget, &QTreeWidget::clicked,
2357             this, [this, detdiag](){
2358       if(detdiag.treeWidget->selectedItems().isEmpty())
2359         detdiag.textBrowser->setText("");
2360       else {
2361         QString name = detdiag.treeWidget->selectedItems().first()->text(0);
2362         QString status = pluginsStatus_map[name];
2363         QString path = PathNames_map[name];
2364         detdiag.textBrowser->setText(QString("Path: %1 \nStatus: %2").arg(path).arg(status));
2365       }
2366     });
2367     dialog.exec();
2368   });
2369   connect(prefdiag.sshButton, &QPushButton::clicked,
2370           this, [this](){
2371     QDialog dialog(this);
2372     Ui::SSHDialog sshdiag;
2373     sshdiag.setupUi(&dialog);
2374 
2375 #ifdef CGAL_USE_SSH
2376     sshdiag.userBox->setEnabled(true);
2377     sshdiag.serverBox->setEnabled(true);
2378     sshdiag.pkBox->setEnabled(true);
2379     sshdiag.privkBox->setEnabled(true);
2380     sshdiag.userEdit->setText(settings.value("ssh_user", QString()).toString());
2381     sshdiag.serverEdit->setText(settings.value("ssh_server", QString()).toString());
2382     sshdiag.publicEdit->setText(settings.value("ssh_public_key", QString()).toString());
2383     sshdiag.privkEdit->setText(settings.value("ssh_priv_key", QString()).toString());
2384     connect(sshdiag.pubButton, &QPushButton::clicked,
2385             this, [this, sshdiag](){
2386       QFileDialog diag(this,
2387                        "Public Key",
2388                        "",
2389                        "All Files (*)");
2390       diag.setFilter(QDir::Hidden|QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot);
2391       if(!diag.exec())
2392         return;
2393       sshdiag.publicEdit->setText(diag.selectedFiles().front());
2394     });
2395     connect(sshdiag.privButton, &QPushButton::clicked,
2396             this, [this, sshdiag](){
2397       QFileDialog diag(this,
2398                        "Private Key",
2399                        "",
2400                        "All Files (*)");
2401       diag.setFilter(QDir::Hidden|QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot);
2402       if(!diag.exec())
2403         return;
2404       sshdiag.privkEdit->setText(diag.selectedFiles().front());
2405     });
2406 #else
2407     sshdiag.userBox->setEnabled(false);
2408     sshdiag.serverBox->setEnabled(false);
2409     sshdiag.pkBox->setEnabled(false);
2410     sshdiag.privkBox->setEnabled(false);
2411 #endif
2412     sshdiag.wsEdit->setText(settings.value("ws_server_url", QString()).toString());
2413 
2414     dialog.exec();
2415     if ( dialog.result() )
2416     {
2417 #ifdef CGAL_USE_SSH
2418       settings.setValue("ssh_user",
2419                         sshdiag.userEdit->text());
2420       settings.setValue("ssh_server",
2421                         sshdiag.serverEdit->text());
2422       settings.setValue("ssh_public_key",
2423                         sshdiag.publicEdit->text());
2424       settings.setValue("ssh_priv_key",
2425                         sshdiag.privkEdit->text());
2426 #endif
2427       settings.setValue("ws_server_url",
2428                         sshdiag.wsEdit->text());
2429       setProperty("ws_url", sshdiag.wsEdit->text());
2430     }
2431   });
2432 
2433   dialog.exec();
2434 
2435   if ( dialog.result() )
2436   {
2437     plugin_blacklist.clear();
2438 
2439     for (std::size_t k=0; k<items.size(); ++k)
2440     {
2441      QTreeWidgetItem* item=items[k];
2442       if (item->checkState(0)==Qt::Unchecked)
2443         plugin_blacklist.insert(item->text(1));
2444     }
2445 
2446     //write settings
2447     settings.setValue("antialiasing",
2448                       prefdiag.antialiasingCheckBox->isChecked());
2449     settings.setValue("offset_update",
2450                       prefdiag.offset_updateCheckBox->isChecked());
2451     settings.setValue("quick_camera_mode",
2452                       prefdiag.quick_cameraCheckBox->isChecked());
2453     settings.setValue("transparency_pass_number",
2454                       viewer->total_pass());
2455     settings.setValue("max_text_items",
2456                       viewer->textRenderer()->getMax_textItems());
2457     settings.setValue("background_color",viewer->backgroundColor().name());
2458     settings.setValue("default_sm_rm", CGAL::Three::Three::modeName(
2459                         CGAL::Three::Three::defaultSurfaceMeshRenderingMode()));
2460     settings.setValue("default_ps_rm", CGAL::Three::Three::modeName(
2461                         CGAL::Three::Three::defaultPointSetRenderingMode()));
2462     settings.setValue("points_size", this->default_point_size);
2463     settings.setValue("normals_length", this->default_normal_length);
2464     settings.setValue("lines_width", this->default_lines_width);
2465 
2466   }
2467   else
2468   {
2469     readSettings();
2470   }
2471 }
2472 
setBackgroundColor()2473 void MainWindow::setBackgroundColor()
2474 {
2475   QColor c =  QColorDialog::getColor();
2476   if(c.isValid()) {
2477     Q_FOREACH(CGAL::QGLViewer* v, CGAL::QGLViewer::QGLViewerPool())
2478     {
2479       if(v == nullptr)
2480         continue;
2481       v->setBackgroundColor(c);
2482       v->update();
2483     }
2484   }
2485 }
2486 
setLighting_triggered()2487 void MainWindow::setLighting_triggered()
2488 {
2489   qobject_cast<Viewer*>(CGAL::Three::Three::activeViewer())->setLighting();
2490 }
2491 
viewerShowObject()2492 void MainWindow::viewerShowObject()
2493 {
2494   Scene_item* item = nullptr;
2495   QAction* sender_action = qobject_cast<QAction*>(sender());
2496   if(sender_action && !sender_action->data().isNull()) {
2497     item = (Scene_item*)sender_action->data().value<void*>();
2498   }
2499   if(item) {
2500     const Scene::Bbox bbox = item->bbox();
2501     CGAL::qglviewer::Vec min((float)bbox.xmin()+viewer->offset().x, (float)bbox.ymin()+viewer->offset().y, (float)bbox.zmin()+viewer->offset().z),
2502         max((float)bbox.xmax()+viewer->offset().x, (float)bbox.ymax()+viewer->offset().y, (float)bbox.zmax()+viewer->offset().z);
2503     viewer->setSceneBoundingBox(min, max);
2504     viewerShow((float)min.x, (float)min.y, (float)min.z,
2505                (float)max.x, (float)max.y, (float)max.z);
2506   }
2507 }
2508 /* to check
2509 QString MainWindow::cameraString() const
2510 {
2511   const CGAL::qglviewer::Vec pos = viewer->camera()->position() - viewer->offset();
2512   const CGAL::qglviewer::Quaternion q = viewer->camera()->orientation();
2513 
2514   return QString("%1 %2 %3 %4 %5 %6 %7")
2515     .arg(pos[0])
2516     .arg(pos[1])
2517     .arg(pos[2])
2518     .arg(q[0])
2519     .arg(q[1])
2520     .arg(q[2])
2521     .arg(q[3]);
2522 }*/
cameraString(CGAL::Three::Viewer_interface * v) const2523 QString MainWindow::cameraString(CGAL::Three::Viewer_interface* v) const
2524 {
2525   return v->dumpCameraCoordinates();
2526 }
2527 
setAddKeyFrameKeyboardModifiers(::Qt::KeyboardModifiers m)2528 void MainWindow::setAddKeyFrameKeyboardModifiers(::Qt::KeyboardModifiers m)
2529 {
2530   viewer->setAddKeyFrameKeyboardModifiers(m);
2531 }
2532 
recenterScene()2533 void MainWindow::recenterScene()
2534 {
2535   //force the recomputaion of the bbox
2536   bbox_need_update = true;
2537   CGAL::qglviewer::Vec min, max;
2538   computeViewerBBox(min, max);
2539   updateViewerBbox(static_cast<Viewer*>(activeViewer()), true, min, max);
2540   activeViewer()->showEntireScene();
2541 }
2542 
on_actionLoadPlugin_triggered()2543 void MainWindow::on_actionLoadPlugin_triggered()
2544 {
2545   //pop a dialog of path selection, get the path and add it to plugins_directory
2546 
2547   QString filters("Library files (*.dll *.DLL *.so *.a *.sl *.dylib *.bundle);;"
2548                   "Any files (*)");
2549 
2550   QStringList paths = QFileDialog::getOpenFileNames(
2551         this,
2552         tr("Select the directory containing your plugins:"),
2553         ".",filters);
2554   Q_FOREACH(QString name, paths)
2555     load_plugin(name, false);
2556 
2557   updateMenus();
2558 }
2559 
recurseExpand(QModelIndex index)2560 void MainWindow::recurseExpand(QModelIndex index)
2561 {
2562   int row = index.row();
2563   if(scene->index(0,0,index).isValid())
2564   {
2565     recurseExpand(scene->index(0,0,index));
2566   }
2567   CGAL::Three::Scene_group_item* group =
2568       qobject_cast<CGAL::Three::Scene_group_item*>(scene->item(scene->getIdFromModelIndex(index)));
2569   if(group && group->isExpanded())
2570   {
2571     sceneView->setExpanded(proxyModel->mapFromSource(index), true);
2572   }
2573   else if (group && !group->isExpanded()){
2574     sceneView->setExpanded(proxyModel->mapFromSource(index), false);
2575   }
2576 
2577   if( index.sibling(row+1,0).isValid())
2578     recurseExpand(index.sibling(row+1,0));
2579 }
restoreCollapseState()2580 void MainWindow::restoreCollapseState()
2581 {
2582   QModelIndex modelIndex = scene->index(0,0,scene->invisibleRootItem()->index());
2583   if(modelIndex.isValid())
2584     recurseExpand(modelIndex);
2585   resetHeader();
2586 }
makeNewGroup()2587 void MainWindow::makeNewGroup()
2588 {
2589   Scene_group_item * group = new Scene_group_item();
2590   scene->addItem(group);
2591   for(Scene::Item_id id : scene->selectionIndices())
2592   {
2593     scene->changeGroup(scene->item(id), group);
2594   }
2595 }
2596 
on_upButton_pressed()2597 void MainWindow::on_upButton_pressed()
2598 {
2599   scene->moveRowUp();
2600 }
2601 
on_downButton_pressed()2602 void MainWindow::on_downButton_pressed()
2603 {
2604   scene->moveRowDown();
2605 }
2606 
recenterSceneView(const QModelIndex & id)2607 void MainWindow::recenterSceneView(const QModelIndex &id)
2608 {
2609   if(id.isValid())
2610   {
2611     // mapFromSource is necessary to convert the QModelIndex received
2612     // from the Scene into a valid QModelIndex in the view, beacause of
2613     // the proxymodel
2614     sceneView->scrollTo(proxyModel->mapFromSource(id));
2615   }
2616 }
2617 
statisticsOnItem()2618 void MainWindow::statisticsOnItem()
2619 {
2620   QApplication::setOverrideCursor(Qt::WaitCursor);
2621 
2622   if (statistics_dlg == nullptr)
2623   {
2624     statistics_dlg = new QDialog(this);
2625     statistics_ui->setupUi(statistics_dlg);
2626     connect(statistics_ui->okButtonBox, SIGNAL(accepted()),
2627             statistics_dlg, SLOT(accept()));
2628     connect(statistics_ui->updateButton, SIGNAL(clicked()),
2629             this, SLOT(statisticsOnItem()));
2630     connect(statistics_ui->exportButton, &QPushButton::clicked,
2631             this, &MainWindow::exportStatistics);
2632   }
2633   statistics_ui->label_htmltab->setText(get_item_stats());
2634 
2635   statistics_dlg->show();
2636   statistics_dlg->raise();
2637 
2638   QApplication::restoreOverrideCursor();
2639 }
2640 
2641 /* Creates a string containing an html table. This string is constructed by appending each parts of each row, so that the data can
2642   depend on the number of selected items. This String is then returned.*/
get_item_stats()2643 QString MainWindow::get_item_stats()
2644 {
2645   //1st step : get all classnames of the selected items
2646   QList<QString> classnames;
2647   Q_FOREACH(int id, scene->selectionIndices())
2648   {
2649     Scene_item* item = scene->item(id);
2650     QString classname = item->property("classname").toString();
2651     if(classname.isEmpty())
2652        classname = item->metaObject()->className();
2653     if(!classnames.contains(classname))
2654       classnames << classname;
2655   }
2656   //2nd step : separate the selection in lists corresponding to their classname
2657   QVector< QList<Scene_item*> > items;
2658   items.resize(classnames.size());
2659   Q_FOREACH(int id, scene->selectionIndices())
2660   {
2661     Scene_item* s_item = scene->item(id);
2662     for(int i=0; i<items.size(); i++)
2663     {
2664       Scene_item* item = scene->item(id);
2665       QString classname = item->property("classname").toString();
2666       if(classname.isEmpty())
2667          classname = item->metaObject()->className();
2668       if(classnames.at(i) == classname)
2669       {
2670         items[i] << s_item;
2671         break;
2672       }
2673     }
2674   }
2675   //last step :: making tables for each type of item
2676   QString str;
2677   for(int i=0; i< classnames.size(); i++)
2678   {
2679     CGAL::Three::Scene_item::Header_data data = items[i].at(0)->header();
2680     int title = 0;
2681     int titles_limit =0;
2682     if(data.titles.size()>0)
2683     {
2684       //1st row : item names
2685       str.append("<html> <table border=1>""<tr><td colspan = 2></td>");
2686       Q_FOREACH(Scene_item* sit, items[i])
2687       {
2688         str.append(QString("<td>%1</td>").arg(sit->name()));
2689       }
2690 
2691 
2692 
2693       for(int j=0; j<data.categories.size(); j++)
2694       {
2695         str.append(QString("<tr><th rowspan=%1> %2 </th>")
2696                    .arg(QString::number(data.categories[j].second))
2697                    .arg(data.categories[j].first));
2698         titles_limit+=data.categories[j].second;
2699         str.append(QString("<td> %1 </td>").arg(data.titles.at(title)));
2700         Q_FOREACH(Scene_item* sit, items[i])
2701         {
2702           str.append(QString("<td>%1</td>").arg(sit->computeStats(title)));
2703         }
2704         title++;
2705         for(;title<titles_limit; title++)
2706         {
2707           str.append(QString("</tr><tr><td> %1 </td>").arg(data.titles.at(title)));
2708           Q_FOREACH(Scene_item* sit, items[i])
2709           {
2710             str.append(QString("<td>%1</td>").arg(sit->computeStats(title)));
2711           }
2712         }
2713 
2714         str.append("</tr>");
2715       }
2716 
2717       str.append(QString("</tr>""</table></html>"));
2718     }
2719   }
2720   return str;
2721 }
2722 
setCollapsed(QModelIndex index)2723 void MainWindow::setCollapsed(QModelIndex index)
2724 {
2725   Q_EMIT collapsed(proxyModel->mapToSource(index));
2726 }
2727 
setExpanded(QModelIndex index)2728 void MainWindow::setExpanded(QModelIndex index)
2729 {
2730   Q_EMIT expanded(proxyModel->mapToSource(index));
2731 }
2732 
2733 
setMaxTextItemsDisplayed(int val)2734 void MainWindow::setMaxTextItemsDisplayed(int val)
2735 {
2736   for(auto v : CGAL::QGLViewer::QGLViewerPool())
2737     qobject_cast<CGAL::Three::Viewer_interface*>(v)->textRenderer()->setMax(val);
2738 }
2739 
resetHeader()2740 void MainWindow::resetHeader()
2741 {
2742   sceneView->header()->setStretchLastSection(false);
2743   scene->invisibleRootItem()->setColumnCount(5);
2744   sceneView->header()->setSectionResizeMode(Scene::NameColumn, QHeaderView::Stretch);
2745   sceneView->header()->setSectionResizeMode(Scene::ColorColumn, QHeaderView::Fixed);
2746   sceneView->header()->setSectionResizeMode(Scene::RenderingModeColumn, QHeaderView::ResizeToContents);
2747   sceneView->header()->setSectionResizeMode(Scene::ABColumn, QHeaderView::Fixed);
2748   sceneView->header()->setSectionResizeMode(Scene::VisibleColumn, QHeaderView::Fixed);
2749 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
2750   sceneView->header()->resizeSection(Scene::ColorColumn, sceneView->header()->fontMetrics().horizontalAdvance("_#_"));
2751   sceneView->resizeColumnToContents(Scene::RenderingModeColumn);
2752   sceneView->header()->resizeSection(Scene::ABColumn, sceneView->header()->fontMetrics().horizontalAdvance(QString("_AB_")));
2753   sceneView->header()->resizeSection(Scene::VisibleColumn, sceneView->header()->fontMetrics().horizontalAdvance(QString("_View_")));
2754 #else
2755   sceneView->header()->resizeSection(Scene::ColorColumn, sceneView->header()->fontMetrics().width("_#_"));
2756   sceneView->resizeColumnToContents(Scene::RenderingModeColumn);
2757   sceneView->header()->resizeSection(Scene::ABColumn, sceneView->header()->fontMetrics().width(QString("_AB_")));
2758   sceneView->header()->resizeSection(Scene::VisibleColumn, sceneView->header()->fontMetrics().width(QString("_View_")));
2759 #endif
2760 }
2761 
reset_default_loaders()2762 void MainWindow::reset_default_loaders()
2763 {
2764   default_plugin_selection.clear();
2765 
2766   const char* prop_name = "Menu modified by MainWindow.";
2767   QMenu* menu = ui->menuFile;
2768   if(!menu)
2769     return;
2770   bool menuChanged = menu->property(prop_name).toBool();
2771   if(!menuChanged) {
2772     menu->setProperty(prop_name, true);
2773   }
2774   QList<QAction*> menuActions = menu->actions();
2775   menu->removeAction(actionResetDefaultLoaders);
2776 }
2777 
insertActionBeforeLoadPlugin(QMenu * menu,QAction * actionToInsert)2778 void MainWindow::insertActionBeforeLoadPlugin(QMenu* menu, QAction* actionToInsert)
2779 {
2780   if(menu)
2781   {
2782     QList<QAction*> menuActions = menu->actions();
2783     if(!menuActions.contains(actionToInsert))
2784       menu->insertAction(ui->actionLoadPlugin, actionToInsert);
2785   }
2786 }
2787 
colorItems()2788 void MainWindow::colorItems()
2789 {
2790   std::size_t nb_files = scene->selectionIndices().size();
2791   if(nb_files<2)
2792     return;
2793   std::vector<QColor> colors_;
2794   colors_.reserve(nb_files);
2795   compute_color_map(scene->item(scene->selectionIndices().last())->color(),
2796                     static_cast<unsigned>(nb_files),
2797                     std::back_inserter(colors_));
2798   std::size_t nb_item = -1;
2799   Q_FOREACH(int id, scene->selectionIndices())
2800   {
2801     scene->item(id)->setColor(colors_[++nb_item]);
2802   }
2803   for(auto v : CGAL::QGLViewer::QGLViewerPool())
2804     v->update();
2805 }
2806 
2807 
exportStatistics()2808 void MainWindow::exportStatistics()
2809 {
2810   std::vector<Scene_item*> items;
2811   Q_FOREACH(int id, getSelectedSceneItemIndices())
2812   {
2813     Scene_item* s_item = scene->item(id);
2814     items.push_back(s_item);
2815   }
2816 
2817   QString str;
2818   Q_FOREACH(Scene_item* sit, items)
2819   {
2820     CGAL::Three::Scene_item::Header_data data = sit->header();
2821     if(data.titles.size()>0)
2822     {
2823       int titles_limit =0;
2824       int title = 0;
2825       str.append(QString("%1: \n").arg(sit->name()));
2826       for(int j=0; j<data.categories.size(); j++)
2827       {
2828         str.append(QString("  %1: \n")
2829                    .arg(data.categories[j].first));
2830         titles_limit+=data.categories[j].second;
2831         for(;title<titles_limit; ++title)
2832         {
2833           str.append(QString("    %1: ").arg(data.titles.at(title)));
2834           str.append(QString("%1\n").arg(sit->computeStats(title)));
2835         }
2836       }
2837     }
2838   }
2839 
2840   QString filename =
2841       QFileDialog::getSaveFileName((QWidget*)sender(),
2842                                    "",
2843                                    QString("Statistics.txt"),
2844                                    "Text Files (*.txt)");
2845   if(filename.isEmpty())
2846     return;
2847   QFile output(filename);
2848   output.open(QIODevice::WriteOnly | QIODevice::Text);
2849 
2850   if(!output.isOpen()){
2851     qDebug() << "- Error, unable to open" << "outputFilename" << "for output";
2852   }
2853   QTextStream outStream(&output);
2854   outStream << str;
2855   output.close();
2856 }
2857 
propagate_action()2858 void MainWindow::propagate_action()
2859 {
2860   QAction* sender = qobject_cast<QAction*>(this->sender());
2861   if(!sender) return;
2862   QString name = sender->text();
2863   Q_FOREACH(Scene::Item_id id, scene->selectionIndices())
2864   {
2865     Scene_item* item = scene->item(id);
2866     Q_FOREACH(QAction* action, item->contextMenu()->actions())
2867     {
2868       if(action->text() == name)
2869       {
2870         action->trigger();
2871         break;
2872       }
2873     }
2874   }
2875 }
2876 
make_fullpath(const QString & filename,bool duplicate=false)2877 QString make_fullpath(const QString& filename, bool duplicate = false)
2878 {
2879   QString fullpath = QString("%1/%2").arg(QDir::tempPath()).arg(filename);
2880   QString tmp_fullpath = fullpath;
2881   if(duplicate)
2882   {
2883     int i=0;
2884     while(QFileInfo(tmp_fullpath).exists())
2885     {
2886       QString basename = QFileInfo(tmp_fullpath).baseName();
2887       QString dir = QFileInfo(tmp_fullpath).dir().path();
2888       QString suffix= QFileInfo(fullpath).completeSuffix();
2889       tmp_fullpath=QString("%1/%2%3.%4").arg(dir).arg(basename).arg(++i).arg(suffix);
2890     }
2891   }
2892   return tmp_fullpath;
2893 }
2894 /*
2895  The two following functions allow to create files from string and strings from files.
2896  This is used as a workaround of the absence of stream management in our I/O system.
2897  The whole to/from Base64 is used to avoid problems with binary formats. Everything is written
2898  as a base64 binary string, and converted back to what it was.
2899 */
file_to_string(const char * filename)2900 QByteArray file_to_string(const char* filename)
2901 {
2902   std::ifstream f(filename, std::ifstream::binary);
2903   // get size of file
2904   f.seekg (0,f.end);
2905   long size = f.tellg();
2906   f.seekg (0);
2907   std::ostringstream ss;
2908   // allocate memory for file content
2909     char* buffer = new char[size];
2910 
2911     // read content of infile
2912     f.read(buffer,size);
2913 
2914     // write to outfile
2915     ss.write(buffer,size);
2916     // release dynamically-allocated memory
2917     delete[] buffer;
2918   //ss.write( << f.rdbuf(); // reading data
2919   f.close();
2920   std::string st = ss.str();
2921   QByteArray ba(st.c_str(), static_cast<int>(st.size()));
2922   return ba;
2923 }
2924 
write_string_to_file(const QString & str,const QString & filename)2925 QString MainWindow::write_string_to_file(const QString& str, const QString &filename)
2926 {
2927   QString fullpath = make_fullpath(filename);
2928   std::ofstream f(fullpath.toStdString().c_str(), std::ofstream::binary);
2929   QByteArray compressed_item(str.toStdString().c_str());
2930   QByteArray item = qUncompress(QByteArray::fromBase64(compressed_item));
2931   QByteArray bb = item;
2932   f.write(bb.constData(),bb.toStdString().size());
2933   f.close();
2934   return fullpath;
2935 }
2936 
on_actionSa_ve_Scene_as_Script_triggered()2937 void MainWindow::on_actionSa_ve_Scene_as_Script_triggered()
2938 {
2939   if(scene->numberOfEntries() == 0)
2940     return;
2941   bool do_upload = false;
2942 #ifdef CGAL_USE_SSH
2943   QString user = settings.value("ssh_user", QString()).toString();
2944   if(!user.isEmpty())
2945   {
2946     QMessageBox::StandardButton doyou =
2947         QMessageBox::question(this, tr("Upload ?"), tr("Do you wish to upload the scene"
2948                                                        " using the SSH preferences ?"));
2949     do_upload = (doyou == QMessageBox::Yes);
2950   }
2951 #endif
2952 
2953   QString filename;
2954 
2955   if(do_upload){
2956     filename = QString("%1/save_scene.js").arg(QDir::tempPath());
2957   }else{
2958     filename = QFileDialog::getSaveFileName(this,
2959                                             "Save the Scene as a Script File",
2960                                             last_saved_dir,
2961                                             "Qt Script files (*.js)");
2962   }
2963   if(!filename.endsWith(".js"))
2964     filename.append(".js");
2965   std::ofstream os(filename.toUtf8(), std::ofstream::binary);
2966   if(!os)
2967     return;
2968   QApplication::setOverrideCursor(Qt::WaitCursor);
2969   std::vector<std::pair<QString, QString> > names;
2970   std::vector<std::pair<QString, QString> > loaders;
2971   std::vector<QColor> colors;
2972   std::vector<int> rendering_modes;
2973   QStringList not_saved;
2974   Polyhedron_demo_io_plugin_interface* camera_plugin = nullptr;
2975   QMap<QString, QVector<QString> > group_children_map;
2976   for(int i = 0; i < scene->numberOfEntries(); ++i)
2977   {
2978     Scene_item* item = scene->item(i);
2979     QString loader;// = item->property("loader_name").toString();
2980     QString ext;
2981     for(Polyhedron_demo_io_plugin_interface* iop : io_plugins)
2982     {
2983       if(iop->name() == "camera_positions_plugin")
2984         camera_plugin = iop;
2985       if(iop->isDefaultLoader(item))
2986       {
2987         QString sf = iop->saveNameFilters().split(";;").first();
2988         //OFF Files (*.off)
2989         QRegularExpression re("\\(\\*\\.(.*)\\)");
2990         QRegularExpressionMatch rem = re.match(sf);
2991         if(!rem.hasMatch())
2992           continue;
2993         ext = rem.captured(1);
2994         //check if it is in a group
2995         if(item->parentGroup())
2996         {
2997           group_children_map[item->parentGroup()->name()].append(item->name());
2998               ;
2999         }
3000         QList<Scene_item*>to_save;
3001         to_save.append(item);
3002         QString savename(tr("%1.%2").arg(item->name()).arg(ext));
3003         QString fullpath = make_fullpath(savename, true);
3004         savename = QFileInfo(fullpath).fileName();
3005         iop->save(QFileInfo(fullpath), to_save);
3006         names.push_back(std::make_pair(savename, item->name()));
3007         loader=iop->name();
3008         break;
3009       }
3010     }
3011     if(loader.isEmpty())
3012     {
3013       QApplication::restoreOverrideCursor();
3014       QMessageBox::warning(this, "", tr("No plugin found for %1. Not saved.").arg(item->name()));
3015       QApplication::setOverrideCursor(Qt::WaitCursor);
3016       continue;
3017     }
3018 
3019     loaders.push_back(std::make_pair(loader, ext));
3020     colors.push_back(item->color());
3021     rendering_modes.push_back(item->renderingMode());
3022   }
3023   bool has_camera_positions = false;
3024   if(camera_plugin)
3025   {
3026     QString fullpath = make_fullpath("camera_tmp.camera.txt");
3027     QList<Scene_item*> dummy;
3028     if(camera_plugin->save(QFileInfo(fullpath), dummy))
3029     {
3030       QByteArray item = file_to_string(fullpath.toStdString().c_str());
3031       os << "var camera_positions= [\'";
3032       os<<qCompress(item, 9).toBase64().toStdString().c_str();
3033       os << "\']\n" ;
3034       //delete temp file
3035       QFile tmp_file(fullpath);
3036       tmp_file.remove();
3037       has_camera_positions =true;
3038     }
3039   }
3040   if(loaders.empty())
3041     return;
3042   //path
3043   os << "var camera = \""<<viewer->dumpCameraCoordinates().toStdString()<<"\";\n";
3044   os << "var items = [";
3045   for(std::size_t i = 0; i< names.size() -1; ++i)
3046   {
3047     QString fullpath = make_fullpath(names[i].first);
3048 
3049     QByteArray item = file_to_string(fullpath.toStdString().c_str());
3050     os<<"[\'";
3051     os<<qCompress(item, 9).toBase64().toStdString().c_str();
3052     os << "\', \'"<<names[i].second.toStdString().c_str()<<"\']," ;
3053     //delete temp file
3054     QFile tmp_file(fullpath);
3055     tmp_file.remove();
3056   }
3057   QString fullpath = make_fullpath(names.back().first);
3058   QByteArray item = file_to_string(fullpath.toStdString().c_str());
3059   os<<"[\'";
3060   os<<qCompress(item, 9).toBase64().toStdString().c_str();
3061   os << "\', \'"<<names.back().second.toStdString().c_str()<<"\']];\n";
3062   //delete temp file
3063   QFile tmp_file(fullpath);
3064   tmp_file.remove();
3065   //group relations
3066   if(!group_children_map.empty())
3067   {
3068     os << "var groups = [";
3069     for(int i = 0; i< group_children_map.size() -1; ++i)
3070     {
3071       QString group_name = group_children_map.keys()[i];
3072       os << "[\'" << group_name.toStdString().c_str()<<"\', [";
3073       for(int j = 0; j<group_children_map[group_name].size()-1; ++j)
3074       {
3075         os << group_children_map[group_name][j].toStdString().c_str()<<", ";
3076       }
3077       os << group_children_map[group_name].back().toStdString().c_str()<<"]],";
3078     }
3079     QString group_name = group_children_map.keys().back();
3080     os << "[\'" << group_name.toStdString().c_str()<<"\', [";
3081     for(int j = 0; j<group_children_map[group_name].size()-1; ++j)
3082     {
3083       os << "\'"<<group_children_map[group_name][j].toStdString().c_str()<<"\', ";
3084     }
3085     os << "\'"<<group_children_map[group_name].back().toStdString().c_str()<<"\']]];\n";
3086   }
3087   //plugin
3088   os << "var loaders = [";
3089   for(std::size_t i = 0; i< names.size() -1; ++i)
3090   {
3091     os << "[\'" << loaders[i].first.toStdString() << "\', \'"<<loaders[i].second.toStdString()<< "\'],";
3092   }
3093   os << "[\'" << loaders.back().first.toStdString() << "\', \'"<<loaders.back().second.toStdString()<< "\']];\n";
3094 
3095   //color
3096   os << "var colors = [";
3097   for(std::size_t i = 0; i< names.size() -1; ++i)
3098   {
3099     os << "[" << colors[i].red() <<", "<< colors[i].green() <<", "<< colors[i].blue() <<"], ";
3100   }
3101   os<<"[" << colors.back().red() <<", "<< colors.back().green() <<", "<< colors.back().blue() <<"]];\n";
3102 
3103   //rendering mode
3104   os << "var rendering_modes = [";
3105   for(std::size_t i = 0; i< names.size() -1; ++i)
3106   {
3107     os << rendering_modes[i] << ", ";
3108   }
3109   os << rendering_modes.back()<<"];\n";
3110   os << "function indexFromName(name) {\n";
3111   os << "  for(var i = 0; i < items.length; i++){\n";
3112   os << "    var itemName=items[i][1];\n";
3113   os << "    if( itemName === name)\n";
3114   os << "    {\n";
3115   os << "        return i;\n";
3116   os << "    }\n";
3117   os << "  };\n";
3118   os << "}\n";
3119   os << "items.forEach(function(item, index, array){\n";
3120   os<<"          var path=items[index][1];\n";
3121   os<<"          path+='.';\n";
3122   os<<"          path+=loaders[index][1];\n";
3123   os<<"          var fullpath = main_window.write_string_to_file(item[0], path);\n";
3124   os<<"          main_window.open(fullpath,loaders[index][0]);\n";
3125   os << "        var it = scene.item(scene.numberOfEntries-1);\n";
3126   os << "        var r = colors[index][0];\n";
3127   os << "        var g = colors[index][1];\n";
3128   os << "        var b = colors[index][2];\n";
3129   os << "        it.setRgbColor(r,g,b);\n";
3130   os << "        it.setRenderingMode(rendering_modes[index]);\n";
3131   os << "});\n";
3132   if(!group_children_map.empty())
3133   {
3134     os << "groups.forEach(function(group, index, array){\n";
3135     os << "    main_window.selectSceneItem(-1);\n";
3136     os << "    var group_name = group[0];\n";
3137     os << "    var item_list = group[1];\n";
3138     os << "    main_window.makeNewGroup();\n";
3139     os << "    var it = scene.item(scene.numberOfEntries-1);\n";
3140     os << "    it.setName(group_name);\n";
3141     os << "    item_list.forEach(function(child, index, array){\n";
3142     os << "        scene.changeGroup(scene.item(indexFromName(child)), it);\n";
3143     os << "    });\n";
3144     os << "});\n";
3145   }
3146   os << "viewer.moveCameraToCoordinates(camera, 0.05);\n";
3147   if(has_camera_positions)
3148   {
3149     os<<"  var path=\"cams.camera.txt\";\n";
3150     os<<"  var fullpath = main_window.write_string_to_file(camera_positions, path);\n";
3151     os<<"  main_window.open(fullpath,\'camera_positions_plugin\');\n";
3152   }
3153   os.close();
3154   if(!not_saved.empty()){
3155     QApplication::restoreOverrideCursor();
3156     QMessageBox::warning(this,
3157                          "Items Not  Saved",
3158                          QString("The following items could not be saved: %1").arg(
3159                            not_saved.join(", ")));
3160     QApplication::setOverrideCursor(Qt::WaitCursor);
3161   }
3162 #ifdef CGAL_USE_SSH
3163   using namespace CGAL::ssh_internal;
3164   if(do_upload)
3165   {
3166     QString server = settings.value("ssh_server", QString()).toString();
3167     QString pk = settings.value("ssh_public_key", QString()).toString();
3168     QString privK = settings.value("ssh_priv_key", QString()).toString();
3169     user = user.trimmed();
3170     server = server.trimmed();
3171     pk = pk.trimmed();
3172     privK=privK.trimmed();
3173     if(user.isEmpty()){
3174       return;
3175     }
3176     QString path;
3177     path = QInputDialog::getText(this,
3178                                  "",
3179                              tr("Enter the name of your scene file."));
3180     if(path.isEmpty())
3181       return;
3182     if(!path.contains("Polyhedron_demo_"))
3183       path.prepend("Polyhedron_demo_");
3184     try{
3185       ssh_session session = nullptr;
3186       bool res = establish_ssh_session_from_agent(session,
3187                                                   user.toStdString().c_str(),
3188                                                   server.toStdString().c_str(),
3189                                                   pk.toStdString().c_str());
3190 
3191       if(!res)
3192       {
3193         bool ok;
3194         QString pass;
3195         pass = QInputDialog::getText(this, "SSH Password",
3196                                      "Enter ssh key password:",
3197                                      QLineEdit::Password,
3198                                      tr(""),
3199                                      &ok);
3200         if(!ok)
3201         {
3202           ssh_free(session);
3203           QApplication::restoreOverrideCursor();
3204           return;
3205         }
3206         pass = pass.trimmed();
3207         res = establish_ssh_session(session,
3208                                     user.toStdString().c_str(),
3209                                     server.toStdString().c_str(),
3210                                     pk.toStdString().c_str(),
3211                                     privK.toStdString().c_str(),
3212                                     pass.toStdString().c_str());
3213       }
3214 
3215       if(!res)
3216       {
3217         QMessageBox::warning(this,
3218                              "Error",
3219                              "The SSH session could not be started.");
3220         ssh_free(session);
3221         QApplication::restoreOverrideCursor();
3222         return;
3223       }
3224       res = push_file(session,path.toStdString().c_str(), filename.toStdString().c_str());
3225       if(!res)
3226       {
3227         QApplication::restoreOverrideCursor();
3228         QMessageBox::warning(this,
3229                              "Error",
3230                              "The file could not be uploaded. Check your console for more information.");
3231         close_connection(session);
3232         ssh_free(session);
3233         return;
3234       }
3235       close_connection(session);
3236       ssh_free(session);
3237       QFile tmp_file(filename);
3238       tmp_file.remove();
3239     } catch( ssh::SshException e )
3240     {
3241       std::cout << "Error during connection : ";
3242       std::cout << e.getError() << std::endl;
3243     }
3244   }
3245 #endif
3246   QApplication::restoreOverrideCursor();
3247 }
setTransparencyPasses(int val)3248 void MainWindow::setTransparencyPasses(int val)
3249 {
3250   viewer->setTotalPass(val);
3251   viewer->update();
3252 }
3253 
toggleFullScreen()3254 void MainWindow::toggleFullScreen()
3255 {
3256   QList<QDockWidget *> dockWidgets = findChildren<QDockWidget *>();
3257   if(visibleDockWidgets.isEmpty())
3258   {
3259     Q_FOREACH(QDockWidget * dock, dockWidgets)
3260     {
3261       if(dock->isVisible())
3262       {
3263         visibleDockWidgets.append(dock);
3264         dock->hide();
3265       }
3266     }
3267   }
3268   else
3269   {
3270     Q_FOREACH(QDockWidget * dock, visibleDockWidgets){
3271       dock->show();
3272     }
3273     visibleDockWidgets.clear();
3274 
3275   }
3276 }
3277 
setDefaultSaveDir()3278 void MainWindow::setDefaultSaveDir()
3279 {
3280   QString dirpath = QFileDialog::getExistingDirectory(this, "Set Default Save as Directory", def_save_dir);
3281   if(!dirpath.isEmpty())
3282     def_save_dir = dirpath;
3283   settings.setValue("default_saveas_dir", def_save_dir);
3284 }
3285 
3286 
setupViewer(Viewer * viewer,SubViewer * subviewer)3287 void MainWindow::setupViewer(Viewer* viewer, SubViewer* subviewer)
3288 {
3289   viewer->textRenderer()->setScene(scene);
3290   viewer->setScene(scene);
3291   connect(scene, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex & )),
3292           viewer, SLOT(update()));
3293   connect(scene, SIGNAL(updated()),
3294           viewer, SLOT(update()));
3295 
3296   QAction* action = subviewer->findChild<QAction*>("actionRecenter");
3297   connect(action, SIGNAL(triggered()),
3298           viewer, SLOT(update()));
3299   connect(action, &QAction::triggered,
3300           subviewer, &SubViewer::recenter);
3301   action= subviewer->findChild<QAction*>("actionLookat");
3302   connect(action, SIGNAL(triggered()),
3303           subviewer, SLOT(lookat()));
3304   action= subviewer->findChild<QAction*>("actionColor");
3305   connect(action, &QAction::triggered,
3306           subviewer, &SubViewer::color);
3307   action= subviewer->findChild<QAction*>("actionDumpCamera");
3308   connect(action, &QAction::triggered,
3309           [this, viewer](){
3310     information(QString("Camera: %1")
3311                 .arg(cameraString(viewer)));
3312   });
3313   action= subviewer->findChild<QAction*>("actionCopyCamera");
3314   connect(action, &QAction::triggered,
3315           [this, viewer](){
3316     qApp->clipboard()->setText(cameraString(viewer));
3317   });
3318   action= subviewer->findChild<QAction*>("actionPasteCamera");
3319   connect(action, &QAction::triggered,
3320           this, [viewer](){
3321     QString s = qApp->clipboard()->text();
3322     viewer->moveCameraToCoordinates(s, 0.5f);
3323   });
3324   action= subviewer->findChild<QAction*>("actionAntiAliasing");
3325   connect(action, SIGNAL(toggled(bool)),
3326           viewer, SLOT(setAntiAliasing(bool)));
3327   action= subviewer->findChild<QAction*>("actionDrawTwoSide");
3328   connect(action, SIGNAL(toggled(bool)),
3329           viewer, SLOT(setTwoSides(bool)));
3330   action= subviewer->findChild<QAction*>("actionQuick");
3331   connect(action, SIGNAL(toggled(bool)),
3332           viewer, SLOT(setFastDrawing(bool)));
3333   action= subviewer->findChild<QAction*>("actionOrtho");
3334   connect(action, SIGNAL(toggled(bool)),
3335           viewer, SLOT(SetOrthoProjection(bool)));
3336   action= subviewer->findChild<QAction*>("actionTotalPass");
3337   connect(action, &QAction::triggered,
3338           this, [this, viewer]() {
3339     bool ok;
3340     int nb = QInputDialog::getInt(this, "Set Maximum Number of Passes",
3341                                   "Enter number of transparency passes:",
3342                                   4, 4, 99, 1, &ok);
3343     if (!ok){
3344       return;
3345     }
3346     viewer->setTotalPass(nb);
3347   });
3348 
3349   action = subviewer->findChild<QAction*>("actionScaleScene");
3350   action->setCheckable(true);
3351   action->setChecked(false);
3352   connect(action, &QAction::triggered,
3353           viewer, &Viewer::scaleScene);
3354 
3355   action= subviewer->findChild<QAction*>("actionBackFrontShading");
3356   connect(action, SIGNAL(toggled(bool)),
3357           viewer, SLOT(setBackFrontShading(bool)));
3358   connect(viewer, SIGNAL(requestContextMenu(QPoint)),
3359           this, SLOT(contextMenuRequested(QPoint)));
3360   connect(viewer, SIGNAL(selected(int)),
3361           this, SLOT(selectSceneItem(int)));
3362   connect(viewer, SIGNAL(selectedPoint(double, double, double)),
3363           this, SLOT(showSelectedPoint(double, double, double)));
3364 
3365   connect(viewer, SIGNAL(selectionRay(double, double, double,
3366                                       double, double, double)),
3367           scene, SIGNAL(selectionRay(double, double, double,
3368                                      double, double, double)));
3369 
3370   connect(viewer, &Viewer::sendMessage,
3371           this, [](QString s){
3372     information(s);
3373   });
3374 
3375 #ifdef CGAL_USE_WEBSOCKETS
3376   action= subviewer->viewer->findChild<QAction*>("actionShareCamera");
3377   connect(action, &QAction::toggled,
3378           this, [this, viewer](bool b)
3379   {
3380     if(!viewer){
3381       return;
3382     }
3383     QString session;
3384     if(b){
3385       bool ok;
3386       session = QInputDialog::getText(
3387             this,"Session",
3388             "Please enter the session name.\n"
3389             "Only the machines that enter the same session name will be connected.\n"
3390             "Several sessions can run simultaneously on a same server. ",
3391             QLineEdit::Normal, QString(), &ok);
3392       if(session.isEmpty() || !ok)
3393       {
3394         viewer->setShareCam(false, session);
3395         return;
3396       }
3397     }
3398     viewer->setShareCam(b, session);
3399   });
3400 #endif
3401 
3402 }
3403 
on_actionAdd_Viewer_triggered()3404 void MainWindow::on_actionAdd_Viewer_triggered()
3405 {
3406   SubViewer* subviewer = new SubViewer(ui->mdiArea, this, viewer);
3407   Viewer* viewer2 = subviewer->viewer;
3408   viewer2->setManipulatedFrame(viewer->manipulatedFrame());
3409   CGAL::qglviewer::Vec min, max;
3410   computeViewerBBox(min, max);
3411   updateViewerBbox(viewer2, true, min, max);
3412   viewer2->setObjectName("viewer2");
3413   connect(viewer2, SIGNAL(doneInitGL(CGAL::Three::Viewer_interface*)),
3414           scene, SLOT(newViewer(CGAL::Three::Viewer_interface*)));
3415   connect(viewer2, &Viewer::contextIsDestroyed,
3416           this, [this, viewer2](){
3417     scene->removeViewer(viewer2);
3418     viewerDestroyed(viewer2);
3419   });
3420 
3421   setupViewer(viewer2, subviewer);
3422   viewer2->camera()->interpolateToFitScene();
3423   subviewer->show();
3424   ui->mdiArea->tileSubWindows();
3425   QPoint pos = viewer_window->pos();
3426   QSize size = viewer_window->size();
3427   viewer_window->move(subviewer->pos());
3428   viewer_window->resize(subviewer->size());
3429   subviewer->move(pos);
3430   subviewer->resize(size);
3431   newViewerCreated(viewer2);
3432 }
3433 
recenterViewer()3434 void MainWindow::recenterViewer()
3435 {
3436   scene->computeBbox();
3437   CGAL::qglviewer::Vec min, max;
3438   computeViewerBBox(min, max);
3439   Viewer* target = qobject_cast<Viewer*>(childAt(cursor().pos()));
3440   if(target)
3441   {
3442     scene->computeBbox();
3443     updateViewerBbox(target, true, min, max);
3444     target->camera()->interpolateToFitScene();
3445   }
3446 }
3447 
updateViewerBbox(Viewer * vi,bool recenter,CGAL::qglviewer::Vec min,CGAL::qglviewer::Vec max)3448 void MainWindow::updateViewerBbox(Viewer *vi, bool recenter,
3449                                   CGAL::qglviewer::Vec min,
3450                                   CGAL::qglviewer::Vec max){
3451   CGAL::qglviewer::Vec center = viewer->camera()->pivotPoint();
3452   vi->setSceneBoundingBox(min,
3453                           max);
3454   if(recenter)
3455   {
3456     vi->resetFov();
3457     vi->camera()->showEntireScene();
3458   }
3459   else
3460   {
3461     vi->camera()->setPivotPoint(center);
3462   }
3463 }
3464 
getDirectChild(QObject * widget)3465 QObject* MainWindow::getDirectChild(QObject* widget)
3466 {
3467 
3468   if(!widget->property("helpText").toString().isEmpty())
3469     return widget;
3470   return getDirectChild(widget->parent());
3471 }
3472 
on_action_Rearrange_Viewers_triggered()3473 void MainWindow::on_action_Rearrange_Viewers_triggered()
3474 {
3475   if(ui->mdiArea->subWindowList().size() == 1)
3476     ui->mdiArea->subWindowList().first()->showMaximized();
3477   else
3478   {
3479     ui->mdiArea->tileSubWindows();
3480     QMdiSubWindow* subviewer = qobject_cast<QMdiSubWindow*>(
3481           ui->mdiArea->childAt(ui->mdiArea->pos()));
3482     if(!subviewer)//should not happen but better safe than sorry
3483     {
3484       return;
3485     }
3486     QPoint pos = viewer_window->pos();
3487     QSize size = viewer_window->size();
3488     viewer_window->move(subviewer->pos());
3489     viewer_window->resize(subviewer->size());
3490     subviewer->move(pos);
3491     subviewer->resize(size);
3492   }
3493 }
3494 
SubViewer(QWidget * parent,MainWindow * mw,Viewer * mainviewer)3495 SubViewer::SubViewer(QWidget *parent, MainWindow* mw, Viewer* mainviewer)
3496   :QMdiSubWindow (parent),
3497     mw(mw),
3498     viewMenu(new QMenu(this)),
3499     is_main(false)
3500 {
3501   if(mainviewer)
3502     viewer = new Viewer(this, mainviewer);
3503   else
3504   {
3505     viewer = new Viewer(this);
3506     is_main = true;
3507   }
3508   setWidget(viewer);
3509   QAction* actionRecenter = new QAction("Re&center Scene",this);
3510   actionRecenter->setObjectName("actionRecenter");
3511   viewMenu->addAction(actionRecenter);
3512   QAction* actionLookat = new QAction("&Look at...",this);
3513   actionLookat->setObjectName("actionLookat");
3514   viewMenu->addAction(actionLookat);
3515   QAction* actionColor = new QAction("Change &Background Color...",this);
3516   actionColor->setObjectName("actionColor");
3517   viewMenu->addAction(actionColor);
3518   QAction* actionDumpCamera = new QAction("&Dump Camera Coordinates",this);
3519   actionDumpCamera->setObjectName("actionDumpCamera");
3520   QAction* actionCopyCamera = new QAction("&Copy Camera",this);
3521   actionCopyCamera->setObjectName("actionCopyCamera");
3522   QAction* actionPasteCamera = new QAction("&Paste Camera",this);
3523   actionPasteCamera->setObjectName("actionPasteCamera");
3524   QMenu* cameraMenu = new QMenu("Ca&mera", mw);
3525   cameraMenu->addAction(actionDumpCamera);
3526   cameraMenu->addAction(actionCopyCamera);
3527   cameraMenu->addAction(actionPasteCamera);
3528   viewMenu->addMenu(cameraMenu);
3529 
3530   QAction* actionAntiAliasing = new QAction("&Antialiasing",this);
3531   actionAntiAliasing->setObjectName("actionAntiAliasing");
3532   actionAntiAliasing->setCheckable(true);
3533   actionAntiAliasing->setChecked(false);
3534   viewMenu->addAction(actionAntiAliasing);
3535   QAction* actionDrawTwoSide = new QAction("Draw &Two Sides",this);
3536   actionDrawTwoSide->setObjectName("actionDrawTwoSide");
3537   actionDrawTwoSide->setCheckable(true);
3538   actionDrawTwoSide->setChecked(false);
3539   viewMenu->addAction(actionDrawTwoSide);
3540   QAction* actionQuick = new QAction("&Quick Camera Mode",this);
3541   actionQuick->setObjectName("actionQuick");
3542   actionQuick->setCheckable(true);
3543   actionQuick->setChecked(true);
3544   viewMenu->addAction(actionQuick);
3545   QAction* actionOrtho = new QAction("&Orthographic Projection",this);
3546   actionOrtho->setObjectName("actionOrtho");
3547   actionOrtho->setCheckable(true);
3548   actionOrtho->setChecked(false);
3549   viewMenu->addAction(actionOrtho);
3550   QAction* actionTotalPass = new QAction("Set Transparency Pass &Number...",this);
3551   actionTotalPass->setObjectName("actionTotalPass");
3552   viewMenu->addAction(actionTotalPass);
3553 #ifdef CGAL_USE_WEBSOCKETS
3554   QAction* actionShareCamera= new QAction("Join &WS Server",viewer);
3555   actionShareCamera->setObjectName("actionShareCamera");
3556   actionShareCamera->setCheckable(true);
3557   actionShareCamera->setChecked(false);
3558   viewMenu->addAction(actionShareCamera);
3559 #endif
3560 
3561   QAction* actionBackFrontShading = new QAction("Activate Back/Front shading.",this);
3562   actionBackFrontShading->setObjectName("actionBackFrontShading");
3563   actionBackFrontShading->setCheckable(true);
3564   actionBackFrontShading->setChecked(false);
3565   viewMenu->addAction(actionBackFrontShading);
3566 
3567   QAction* actionScaleScene = new QAction("&Scale the Scene...",this);
3568   actionScaleScene->setObjectName("actionScaleScene");
3569   viewMenu->addAction(actionScaleScene);
3570 
3571   if(mainviewer)
3572     setAttribute(Qt::WA_DeleteOnClose);
3573   setWindowIcon(QIcon(":/cgal/icons/resources/menu.png"));
3574   setSystemMenu(viewMenu);
3575 }
3576 
~SubViewer()3577 SubViewer::~SubViewer()
3578 {
3579   viewer->deleteLater();
3580 }
3581 
recenter()3582 void SubViewer::recenter()
3583 {
3584   CGAL::qglviewer::Vec min, max;
3585   mw->computeViewerBBox(min, max);
3586   mw->updateViewerBbox(viewer, true, min, max);
3587   viewer->camera()->interpolateToFitScene();
3588 }
3589 
lookat()3590 void SubViewer::lookat()
3591 {
3592   Show_point_dialog dialog(mw);
3593   dialog.setWindowTitle(tr("Look at..."));
3594   int i = dialog.exec();
3595   if( i == QDialog::Accepted &&
3596       dialog.has_correct_coordinates() )
3597   {
3598     if (viewer->camera()->frame()->isSpinning())
3599       viewer->camera()->frame()->stopSpinning();
3600     mw->viewerShow(viewer,
3601                    (float)dialog.get_x() + viewer->offset().x,
3602                    (float)dialog.get_y() + viewer->offset().y,
3603                    (float)dialog.get_z() + viewer->offset().z);
3604   }
3605 }
3606 
color()3607 void SubViewer::color()
3608 {
3609   QColor c =  QColorDialog::getColor();
3610   if(c.isValid()) {
3611     viewer->setBackgroundColor(c);
3612     viewer->update();
3613   }
3614 }
3615 
closeEvent(QCloseEvent * closeEvent)3616 void SubViewer::closeEvent(QCloseEvent *closeEvent)
3617 {
3618 
3619   if(is_main)
3620   {
3621     QMessageBox::information(mw, "", "This is the main viewer. It cannot be closed.");
3622     closeEvent->ignore();
3623   }
3624   else
3625     QWidget::closeEvent(closeEvent);
3626 }
changeEvent(QEvent * event)3627 void SubViewer::changeEvent(QEvent *event)
3628 {
3629   QMdiSubWindow::changeEvent(event);
3630   if(event->type() == QEvent::WindowStateChange)
3631   {
3632     if(isMaximized())
3633     {
3634       QMenu* menu = mw->findChild<QMenu*>("menuView");
3635       Q_FOREACH(QAction* action, viewMenu->actions())
3636       {
3637         menu->addAction(action);
3638       }
3639       setWindowFlags(
3640               Qt::SubWindow
3641               | Qt::CustomizeWindowHint
3642               | Qt::WindowMaximizeButtonHint
3643               //| Qt::WindowSystemMenuHint
3644               | Qt::WindowTitleHint
3645               );
3646       QAction* action = mw->findChild<QAction*>("action_Rearrange_Viewers");
3647       action->setVisible(false);
3648       viewer->update();
3649     }
3650     else
3651     {
3652       QMenu* menu = mw->findChild<QMenu*>("menuView");
3653       Q_FOREACH(QAction* action, viewMenu->actions())
3654       {
3655         menu->removeAction(action);
3656       }
3657       setWindowFlags(
3658               Qt::SubWindow
3659               | Qt::CustomizeWindowHint
3660               | Qt::WindowMaximizeButtonHint
3661               | Qt::WindowSystemMenuHint
3662               | Qt::WindowTitleHint
3663               );
3664       QAction* action = mw->findChild<QAction*>("action_Rearrange_Viewers");
3665       action->setVisible(true);
3666       for(auto v : CGAL::QGLViewer::QGLViewerPool())
3667         v->update();
3668     }
3669   }
3670 }
3671 
invalidate_bbox(bool do_recenter)3672 void MainWindow::invalidate_bbox(bool do_recenter)
3673 {
3674   bbox_need_update = true;
3675   if(do_recenter)
3676     updateViewersBboxes(true);
3677 }
3678 
on_action_Save_triggered()3679 void MainWindow::on_action_Save_triggered()
3680 {
3681   if(QMessageBox::question(this, "Save", "Are you sure you want to override these files ?")
3682      == QMessageBox::No)
3683     return;
3684   QList<Scene_item*> to_save;
3685 
3686   for(Scene::Item_id id : scene->selectionIndices())
3687   {
3688     Scene_item* item = scene->item(id);
3689     if(!item->property("source filename").toString().isEmpty())
3690     {
3691       QString filename = item->property("source filename").toString();
3692       to_save.append(item);
3693       save(filename, to_save);
3694     }
3695   }
3696 }
3697 
on_actionLoad_a_Scene_from_a_Script_File_triggered()3698 void MainWindow::on_actionLoad_a_Scene_from_a_Script_File_triggered()
3699 {
3700   bool do_download = false;
3701   QString filename;
3702 #ifdef CGAL_USE_SSH
3703   QString user = settings.value("ssh_user", QString()).toString();
3704 
3705   if(!user.isEmpty())
3706   {
3707     QMessageBox::StandardButton doyou =
3708         QMessageBox::question(this, tr("Download ?"), tr("Do you wish to download the scene"
3709                                                          " using the SSH preferences ?"));
3710     do_download= (doyou == QMessageBox::Yes);
3711   }
3712 #endif
3713 
3714   if(do_download)
3715   {
3716     #ifdef CGAL_USE_SSH
3717     using namespace CGAL::ssh_internal;
3718     QString server = settings.value("ssh_server", QString()).toString();
3719     QString pk = settings.value("ssh_public_key", QString()).toString();
3720     QString privK = settings.value("ssh_priv_key", QString()).toString();
3721     user = user.trimmed();
3722     server = server.trimmed();
3723     pk = pk.trimmed();
3724     privK=privK.trimmed();
3725 
3726     try{
3727       ssh_session session = nullptr;
3728       bool res = establish_ssh_session_from_agent(session,
3729                                                   user.toStdString().c_str(),
3730                                                   server.toStdString().c_str(),
3731                                                   pk.toStdString().c_str());
3732       if(!res){
3733         bool ok;
3734         QString pass= QInputDialog::getText(this, "SSH Password",
3735                                      "Enter ssh key password:",
3736                                      QLineEdit::Password,
3737                                      tr(""),
3738                                      &ok);
3739         if(!ok)
3740         {
3741           ssh_free(session);
3742           return;
3743         }
3744         pass = pass.trimmed();
3745         res = establish_ssh_session(session,
3746                                     user.toStdString().c_str(),
3747                                     server.toStdString().c_str(),
3748                                     pk.toStdString().c_str(),
3749                                     privK.toStdString().c_str(),
3750                                     pass.toStdString().c_str());
3751       }
3752       if(!res)
3753       {
3754         QMessageBox::warning(this,
3755                              "Error",
3756                              "The SSH session could not be started.");
3757         ssh_free(session);
3758         return;
3759       }
3760       QStringList names;
3761       if(!CGAL::ssh_internal::explore_the_galaxy(session, names))
3762       {
3763         QMessageBox::warning(this,
3764                              "Error",
3765                              "Could not find remote directory.");
3766       }
3767       QString path;
3768       bool ok;
3769       path = QInputDialog::getItem(this,
3770                                    "Choose a file",
3771                                    tr("Choose the scene file."),
3772                                    names,0,true, &ok);
3773       filename = QString("%1/load_scene.js").arg(QDir::tempPath());
3774       if(path.isEmpty() || !ok)
3775       {
3776         ssh_free(session);
3777         return;
3778       }
3779       path.prepend("Polyhedron_demo_");
3780       path = tr("/tmp/%2").arg(path);
3781       res = pull_file(session,path.toStdString().c_str(), filename.toStdString().c_str());
3782       if(!res)
3783       {
3784         QMessageBox::warning(this,
3785                              "Error",
3786                              "The file could not be fetched. Check your console for more info.");
3787         close_connection(session);
3788         ssh_free(session);
3789         return;
3790       }
3791       close_connection(session);
3792       ssh_free(session);
3793     } catch( ssh::SshException e )
3794     {
3795       std::cout << "Error during connection : ";
3796       std::cout << e.getError() << std::endl;
3797     }
3798     #endif
3799   }
3800   else
3801   {
3802     filename =  QFileDialog::getOpenFileName(
3803           this,
3804           tr("Select a Whole Scene file..."),
3805           ".",
3806           "Whole Scene files (*.js)");
3807     if(filename.isEmpty())
3808       return;
3809   }
3810   loadScript(QFileInfo(filename));
3811   if(do_download){
3812     QFile tmp_file(filename);
3813     tmp_file.remove();
3814   }
3815 }
3816