#include #include #include #include #include #include #include #include #include #include #include #include #include #include "mapHandler.h" #include "schnapps.h" #include "controlDock_cameraTab.h" #include "controlDock_mapTab.h" #include "controlDock_pluginTab.h" #include "camera.h" #include "view.h" #include "texture.h" #include "plugin.h" #include "plugin_interaction.h" #include "plugin_processing.h" #include "plugins_default_directory.h" #include "Topology/generic/genericmap.h" namespace CGoGN { namespace SCHNApps { SCHNApps::SCHNApps(const QString& appPath, PythonQtObjectPtr& pythonContext, PythonQtScriptingConsole& pythonConsole) : QMainWindow(), m_pyRecording(NULL), m_pyRecFile(NULL), m_appPath(appPath), m_pythonContext(pythonContext), m_pythonConsole(pythonConsole), m_firstView(NULL), m_selectedView(NULL) { GenericMap::initAllStatics(&m_sp); this->setupUi(this); // create & setup control dock m_controlDock = new QDockWidget("Control Dock", this); m_controlDock->setAllowedAreas(Qt::LeftDockWidgetArea); m_controlDock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); m_controlDock->setMaximumWidth(250); m_controlDockTabWidget = new QTabWidget(m_controlDock); m_controlDockTabWidget->setObjectName("ControlDockTabWidget"); m_controlDockTabWidget->setLayoutDirection(Qt::LeftToRight); m_controlDockTabWidget->setTabPosition(QTabWidget::North); m_controlDockTabWidget->setMovable(true); addDockWidget(Qt::LeftDockWidgetArea, m_controlDock); m_controlDock->setVisible(true); m_controlDock->setWidget(m_controlDockTabWidget); m_controlCameraTab = new ControlDock_CameraTab(this); m_controlDockTabWidget->addTab(m_controlCameraTab, m_controlCameraTab->title()); m_controlMapTab = new ControlDock_MapTab(this); m_controlDockTabWidget->addTab(m_controlMapTab, m_controlMapTab->title()); m_controlPluginTab = new ControlDock_PluginTab(this); m_controlDockTabWidget->addTab(m_controlPluginTab, m_controlPluginTab->title()); connect(action_ShowHideControlDock, SIGNAL(triggered()), this, SLOT(showHideControlDock())); // create & setup plugin dock m_pluginDock = new QDockWidget("Plugins Dock", this); m_pluginDock->setAllowedAreas(Qt::RightDockWidgetArea); m_pluginDock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); m_pluginDockTabWidget = new QTabWidget(m_pluginDock); m_pluginDockTabWidget->setObjectName("PluginDockTabWidget"); m_pluginDockTabWidget->setLayoutDirection(Qt::LeftToRight); m_pluginDockTabWidget->setTabPosition(QTabWidget::East); m_pluginDockTabWidget->setMovable(true); addDockWidget(Qt::RightDockWidgetArea, m_pluginDock); m_pluginDock->setVisible(false); m_pluginDock->setWidget(m_pluginDockTabWidget); connect(action_ShowHidePluginDock, SIGNAL(triggered()), this, SLOT(showHidePluginDock())); // create & setup python dock m_pythonDock = new QDockWidget("Python Dock", this); m_pythonDock->setAllowedAreas(Qt::BottomDockWidgetArea); m_pythonDock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); addDockWidget(Qt::BottomDockWidgetArea, m_pythonDock); m_pythonDock->setVisible(false); m_pythonDock->setWidget(&m_pythonConsole); connect(action_ShowHidePythonDock, SIGNAL(triggered()), this, SLOT(showHidePythonDock())); connect(action_LoadPythonScript, SIGNAL(triggered()), this, SLOT(loadPythonScriptFromFileDialog())); connect(action_Python_Recording, SIGNAL(triggered()), this, SLOT(beginPyRecording())); connect(action_Append_Python_Recording, SIGNAL(triggered()), this, SLOT(appendPyRecording())); action_Python_Recording->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_P)); action_Append_Python_Recording->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_P)); // create & setup central widget (views) m_centralLayout = new QVBoxLayout(centralwidget); m_rootSplitter = new QSplitter(centralwidget); b_rootSplitterInitialized = false; m_centralLayout->addWidget(m_rootSplitter); if (Utils::GLSLShader::CURRENT_OGL_VERSION >= 3) { QGLFormat glFormat; glFormat.setVersion( Utils::GLSLShader::MAJOR_OGL_CORE, Utils::GLSLShader::MINOR_OGL_CORE); glFormat.setProfile( QGLFormat::CoreProfile ); // Requires >=Qt-4.8.0 glFormat.setSampleBuffers( true ); QGLFormat::setDefaultFormat(glFormat); } m_firstView = addView(); setSelectedView(m_firstView); m_rootSplitter->addWidget(m_firstView); // connect basic actions connect(action_AboutSCHNApps, SIGNAL(triggered()), this, SLOT(aboutSCHNApps())); connect(action_AboutCGoGN, SIGNAL(triggered()), this, SLOT(aboutCGoGN())); // register a first plugins directory // registerPluginsDirectory(m_appPath + QString("/../lib")); // using macro in plugins_default_directory.h generated by cmake for lib/Release or lib/Debug auto selection ! registerPluginsDirectory(QString(PLUGINS_DEFAULT_DIRECTORY)); } SCHNApps::~SCHNApps() { // TODO check that cameras are destructed m_cameras.clear(); } /********************************************************* * MANAGE CAMERAS *********************************************************/ Camera* SCHNApps::addCamera(const QString& name) { if (m_cameras.contains(name)) return NULL; Camera* camera = new Camera(name, this); m_cameras.insert(name, camera); DEBUG_EMIT("cameraAdded"); emit(cameraAdded(camera)); return camera; } Camera* SCHNApps::addCamera() { return addCamera(QString("camera_") + QString::number(Camera::cameraCount)); } void SCHNApps::removeCamera(const QString& name) { Camera* camera = getCamera(name); if (camera && !camera->isUsed()) { m_cameras.remove(name); DEBUG_EMIT("cameraRemoved"); emit(cameraRemoved(camera)); delete camera; } } Camera* SCHNApps::getCamera(const QString& name) const { if (m_cameras.contains(name)) return m_cameras[name]; else return NULL; } /********************************************************* * MANAGE VIEWS *********************************************************/ void SCHNApps::redrawAllViews() { foreach(View *v, m_views) { v->updateGL(); } } View* SCHNApps::addView(const QString& name) { if (m_views.contains(name)) return NULL; View* view = new View(name, this, m_firstView); m_views.insert(name, view); DEBUG_EMIT("viewAdded"); emit(viewAdded(view)); // RECORDING if (m_pyRecording) *m_pyRecording << view->getName() << " = schnapps.getView(\"" << view->getName() << "\");" << endl; return view; } View* SCHNApps::addView() { return addView(QString("view_") + QString::number(View::viewCount)); } void SCHNApps::removeView(const QString& name) { if (m_views.contains(name)) { if(m_views.count() > 1) { View* view = m_views[name]; if(view == m_firstView) { ViewSet::const_iterator i = m_views.constBegin(); while (i != m_views.constEnd()) { if(i.value() != view) { m_firstView = i.value(); i = m_views.constEnd(); } else ++i; } } if(view == m_selectedView) setSelectedView(m_firstView); m_views.remove(name); DEBUG_EMIT("viewRemoved"); emit(viewRemoved(view)); delete view; } } } View* SCHNApps::getView(const QString& name) const { if (m_views.contains(name)) return m_views[name]; else return NULL; } void SCHNApps::setSelectedView(View* view) { int currentTab = m_pluginDockTabWidget->currentIndex(); if(m_selectedView) { foreach(PluginInteraction* p, m_selectedView->getLinkedPlugins()) disablePluginTabWidgets(p); disconnect(m_selectedView, SIGNAL(pluginLinked(PluginInteraction*)), this, SLOT(enablePluginTabWidgets(PluginInteraction*))); disconnect(m_selectedView, SIGNAL(pluginUnlinked(PluginInteraction*)), this, SLOT(disablePluginTabWidgets(PluginInteraction*))); } View* oldSelected = m_selectedView; m_selectedView = view; if (oldSelected) oldSelected->hideDialogs(); foreach(PluginInteraction* p, m_selectedView->getLinkedPlugins()) enablePluginTabWidgets(p); connect(m_selectedView, SIGNAL(pluginLinked(PluginInteraction*)), this, SLOT(enablePluginTabWidgets(PluginInteraction*))); connect(m_selectedView, SIGNAL(pluginUnlinked(PluginInteraction*)), this, SLOT(disablePluginTabWidgets(PluginInteraction*))); m_pluginDockTabWidget->setCurrentIndex(currentTab); DEBUG_EMIT("selectedViewChanged"); emit(selectedViewChanged(oldSelected, m_selectedView)); if(oldSelected) oldSelected->updateGL(); m_selectedView->updateGL(); } void SCHNApps::splitView(const QString& name, Qt::Orientation orientation) { // RECORDING if (m_pyRecording) *m_pyRecording << "schnapps.splitView(\"" << name << "\", " << orientation << ");" << endl; View* newView = addView(); View* view = m_views[name]; QSplitter* parent = (QSplitter*)(view->parentWidget()); if(parent == m_rootSplitter && !b_rootSplitterInitialized) { m_rootSplitter->setOrientation(orientation); b_rootSplitterInitialized = true; } if(parent->orientation() == orientation) parent->insertWidget(parent->indexOf(view)+1, newView); else { int idx = parent->indexOf(view); view->setParent(NULL); QSplitter* spl = new QSplitter(orientation); spl->addWidget(view); spl->addWidget(newView); parent->insertWidget(idx, spl); } } /********************************************************* * MANAGE PLUGINS *********************************************************/ void SCHNApps::registerPluginsDirectory(const QString& path) { #ifdef WIN32 #ifdef _DEBUG QDir directory(path+QString("Debug/")); #else QDir directory(path + QString("Release/")); #endif #else QDir directory(path); #endif if(directory.exists()) { QStringList filters; filters << "lib*.so"; filters << "lib*.dylib"; filters << "*.dll"; QStringList pluginFiles = directory.entryList(filters, QDir::Files); foreach(QString pluginFile, pluginFiles) { QFileInfo pfi(pluginFile); #ifdef WIN32 QString pluginName = pfi.baseName(); #else QString pluginName = pfi.baseName().remove(0, 3); #endif QString pluginFilePath = directory.absoluteFilePath(pluginFile); if(!m_availablePlugins.contains(pluginName)) { m_availablePlugins.insert(pluginName, pluginFilePath); DEBUG_EMIT("pluginAvailableAdded"); emit(pluginAvailableAdded(pluginName)); } } } } Plugin* SCHNApps::enablePlugin(const QString& pluginName) { // RECORDING if (m_pyRecording) *m_pyRecording << pluginName << " = schnapps.enablePlugin(\"" << pluginName << "\");" << endl; if (m_plugins.contains(pluginName)) return m_plugins[pluginName]; if (m_availablePlugins.contains(pluginName)) { QString pluginFilePath = m_availablePlugins[pluginName]; QPluginLoader loader(pluginFilePath); // if the loader loads a plugin instance if (QObject* pluginObject = loader.instance()) { Plugin* plugin = qobject_cast(pluginObject); // set the plugin with correct parameters (name, filepath, SCHNApps) plugin->setName(pluginName); plugin->setFilePath(pluginFilePath); plugin->setSCHNApps(this); // then we call its enable() methods if (plugin->enable()) { // if it succeeded we reference this plugin m_plugins.insert(pluginName, plugin); statusbar->showMessage(pluginName + QString(" successfully loaded."), 2000); DEBUG_EMIT("pluginEnabled"); emit(pluginEnabled(plugin)); // menubar->repaint(); // method success return plugin; } else { delete plugin; return NULL; } } // if loading fails else { std::cout << "loader.instance() failed.." << std::endl; return NULL; } } else { return NULL; } } void SCHNApps::disablePlugin(const QString& pluginName) { if (m_plugins.contains(pluginName)) { // RECORDING if (m_pyRecording) *m_pyRecording << "schnapps.disablePlugin(\"" << pluginName << "\");" << endl; Plugin* plugin = m_plugins[pluginName]; // remove plugin dock tabs foreach(QWidget* tab, m_pluginTabs[plugin]) removePluginDockTab(plugin, tab); // remove plugin menu actions foreach(QAction* action, m_pluginMenuActions[plugin]) removeMenuAction(plugin, action); // unlink linked views (for interaction plugins) PluginInteraction* pi = dynamic_cast(plugin); if(pi) { foreach(View* view, pi->getLinkedViews()) view->unlinkPlugin(pi); } // call disable() method and dereference plugin plugin->disable(); m_plugins.remove(pluginName); QPluginLoader loader(plugin->getFilePath()); loader.unload(); statusbar->showMessage(pluginName + QString(" successfully unloaded."), 2000); DEBUG_EMIT("pluginDisabled"); emit(pluginDisabled(plugin)); delete plugin; } } Plugin* SCHNApps::getPlugin(const QString& name) const { if (m_plugins.contains(name)) return m_plugins[name]; else return NULL; } void SCHNApps::addPluginDockTab(Plugin* plugin, QWidget* tabWidget, const QString& tabText) { if(tabWidget && !m_pluginTabs[plugin].contains(tabWidget)) { int currentTab = m_pluginDockTabWidget->currentIndex(); int idx = m_pluginDockTabWidget->addTab(tabWidget, tabText); m_pluginDock->setVisible(true); PluginInteraction* pi = dynamic_cast(plugin); if(pi) { if(pi->isLinkedToView(m_selectedView)) m_pluginDockTabWidget->setTabEnabled(idx, true); else m_pluginDockTabWidget->setTabEnabled(idx, false); } if(currentTab != -1) m_pluginDockTabWidget->setCurrentIndex(currentTab); m_pluginTabs[plugin].append(tabWidget); } } void SCHNApps::removePluginDockTab(Plugin* plugin, QWidget *tabWidget) { if(tabWidget && m_pluginTabs[plugin].contains(tabWidget)) { m_pluginDockTabWidget->removeTab(m_pluginDockTabWidget->indexOf(tabWidget)); m_pluginTabs[plugin].removeOne(tabWidget); } } void SCHNApps::enablePluginTabWidgets(PluginInteraction* plugin) { if(m_pluginTabs.contains(plugin)) { foreach(QWidget* w, m_pluginTabs[plugin]) m_pluginDockTabWidget->setTabEnabled(m_pluginDockTabWidget->indexOf(w), true); } } void SCHNApps::disablePluginTabWidgets(PluginInteraction* plugin) { if(m_pluginTabs.contains(plugin)) { foreach(QWidget* w, m_pluginTabs[plugin]) m_pluginDockTabWidget->setTabEnabled(m_pluginDockTabWidget->indexOf(w), false); } } /********************************************************* * MANAGE MAPS *********************************************************/ MapHandlerGen* SCHNApps::addMap(const QString& name, unsigned int dim) { if (m_maps.contains(name)) return NULL; MapHandlerGen* mh = NULL; switch(dim) { case 2 : { PFP2::MAP* map = new PFP2::MAP(); mh = new MapHandler(name, this, map); break; } case 3 : { PFP3::MAP* map = new PFP3::MAP(); mh = new MapHandler(name, this, map); break; } } m_maps.insert(name, mh); DEBUG_EMIT("mapAdded"); emit(mapAdded(mh)); return mh; } void SCHNApps::removeMap(const QString& name) { if (m_maps.contains(name)) { MapHandlerGen* map = m_maps[name]; foreach(View* view, map->getLinkedViews()) { view->unlinkMap(map); } m_maps.remove(name); DEBUG_EMIT("mapRemoved"); emit(mapRemoved(map)); // unselect map if it is removed if (this->getSelectedMap() == map) setSelectedMap(QString("NONE")); delete map; } } void SCHNApps::setSelectedMap(const QString& mapName) { m_controlMapTab->setSelectedMap(mapName); } MapHandlerGen* SCHNApps::getMap(const QString& name) const { if (m_maps.contains(name)) return m_maps[name]; else return NULL; } MapHandlerGen* SCHNApps::getSelectedMap() const { return m_controlMapTab->getSelectedMap(); } unsigned int SCHNApps::getCurrentOrbit() const { return m_controlMapTab->getCurrentOrbit(); } CellSelectorGen* SCHNApps::getSelectedSelector(unsigned int orbit) const { return m_controlMapTab->getSelectedSelector(orbit); } /********************************************************* * MANAGE TEXTURES *********************************************************/ Texture* SCHNApps::getTexture(const QString& image) { if(m_textures.contains(image)) { Texture* t = m_textures[image]; t->ref++; return t; } else { Texture* t = NULL; QImage img(image); if(!img.isNull()) { GLuint texID = m_firstView->bindTexture(img); t = new Texture(texID, img.size(), 1); m_textures.insert(image, t); } return t; } } void SCHNApps::releaseTexture(const QString& image) { if(m_textures.contains(image)) { Texture* t = m_textures[image]; t->ref--; if(t->ref == 0) { m_firstView->deleteTexture(m_textures[image]->texID); m_textures.remove(image); delete t; } } } /********************************************************* * MANAGE MENU ACTIONS *********************************************************/ void SCHNApps::addMenuAction(Plugin* plugin, const QString& menuPath, QAction* action) { if(action && !menuPath.isEmpty() && !m_pluginMenuActions[plugin].contains(action)) { // extracting all the substring separated by ';' QStringList stepNames = menuPath.split(";"); stepNames.removeAll(""); unsigned int nbStep = stepNames.count(); // if only one substring: error + failure // No action directly in the menu bar if (nbStep >= 1) { unsigned int i = 0; QMenu* lastMenu = NULL; foreach(QString step, stepNames) { ++i; if (i < nbStep) // if not last substring (= menu) { // try to find an existing submenu with step name bool found = false; QList actions; if(i == 1) actions = menubar->actions(); else actions = lastMenu->actions(); foreach(QAction* action, actions) { QMenu* subMenu = action->menu(); if (subMenu && subMenu->title() == step) { lastMenu = subMenu; found = true; break; } } if (!found) { QMenu* newMenu; if(i == 1) { newMenu = menubar->addMenu(step); newMenu->setParent(menubar); } else { newMenu = lastMenu->addMenu(step); newMenu->setParent(lastMenu); } lastMenu = newMenu; } } else // if last substring (= action name) { lastMenu->addAction(action); action->setText(step); action->setParent(lastMenu); } } } // just for update the menu in buggy Qt5 on macOS #if (defined CGOGN_APPLE) && ((QT_VERSION>>16) == 5) QMenu* fakemenu = menuBar()->addMenu("X"); delete fakemenu; #endif m_pluginMenuActions[plugin].append(action); } } void SCHNApps::removeMenuAction(Plugin* plugin, QAction *action) { if(action) { action->setEnabled(false); m_pluginMenuActions[plugin].removeOne(action); // parent of the action // which is an instance of QMenu if the action was created // using the addMenuActionMethod() QObject* parent = action->parent(); delete action; while(parent != NULL) { QMenu* parentMenu = dynamic_cast(parent); if(parentMenu && parentMenu->actions().empty()) { parent = parent->parent(); delete parentMenu; } else parent = NULL; } } } void SCHNApps::aboutSCHNApps() { QString str("SCHNApps:\nS... CGoGN Holder for Nice Applications\n" "Web site: http://cgogn.unistra.fr \n" "Contact information: cgogn@unistra.fr"); QMessageBox::about(this, "About SCHNApps", str); } void SCHNApps::aboutCGoGN() { QString str("CGoGN:\nCombinatorial and Geometric modeling\n" "with Generic N-dimensional Maps\n" "Web site: http://cgogn.unistra.fr \n" "Contact information: cgogn@unistra.fr"); QMessageBox::about(this, "About CGoGN", str); } void SCHNApps::showHideControlDock() { m_controlDock->setVisible(m_controlDock->isHidden()); } void SCHNApps::showHidePluginDock() { m_pluginDock->setVisible(m_pluginDock->isHidden()); } void SCHNApps::showHidePythonDock() { m_pythonDock->setVisible(m_pythonDock->isHidden()); } void SCHNApps::loadPythonScriptFromFile(const QString& fileName) { #ifdef WIN32 // NOT VERY NICE BUT ONLY WAY ON WINDOWS TO LOAD SCRIPT FILES QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QTextStream in(&file); while (!in.atEnd()) { QString line = in.readLine(); if (line.size() >= 2) { int j = 0; while (line[j] == ' ') ++j; QString spaces(4 - j, QChar(' ')); // need 4 spaces at begin of line line.replace(QChar('\\'), QChar('/')); // replace \ by / in win path m_pythonConsole.consoleMessage(spaces + line); m_pythonConsole.executeLine(false); } } #else QFileInfo fi(fileName); if(fi.exists()) m_pythonContext.evalFile(fi.filePath()); #endif } void SCHNApps::loadPythonScriptFromFileDialog() { QString fileName = QFileDialog::getOpenFileName(this, "Load Python script", getAppPath(), "Python script (*.py)"); loadPythonScriptFromFile(fileName); } void SCHNApps::beginPyRecording() { if (m_pyRecording != NULL) // WRITE & CLOSE { QTextStream out(m_pyRecFile); out << m_pyRecording->readAll(); m_pyRecFile->close(); delete m_pyRecording; delete m_pyRecFile; m_pyRecording = NULL; m_pyRecFile = NULL; statusbar->showMessage(QString(" Stop recording python"), 2000); return; } QString fileName = QFileDialog::getSaveFileName(this, "Save python script", this->getAppPath(), " python script (*.py)"); if (fileName.size() != 0) { m_pyRecFile = new QFile(fileName); if (!m_pyRecFile->open(QIODevice::WriteOnly | QIODevice::Text)) return; m_pyRecording = new QTextStream(m_pyRecFile); foreach(View *v, m_views) *m_pyRecording << v->getName() << " = schnapps.getView(\"" << v->getName() << "\");" << endl; foreach(MapHandlerGen *m, m_maps) *m_pyRecording << m->getName() << " = schnapps.getMap(\"" << m->getName() << "\");" << endl; statusbar->showMessage(QString(" Recording python in ") + fileName); } else { m_pyRecFile = NULL; m_pyRecording = NULL; statusbar->showMessage(QString(" Cancel recording python"), 2000); } } void SCHNApps::appendPyRecording() { if (m_pyRecording != NULL) // WRITE & CLOSE { QTextStream out(m_pyRecFile); out << m_pyRecording->readAll(); m_pyRecFile->close(); delete m_pyRecording; delete m_pyRecFile; m_pyRecording = NULL; m_pyRecFile = NULL; statusbar->showMessage(QString(" Stop recording python"), 2000); return; } QString fileName = QFileDialog::getSaveFileName(this, "Append python script", this->getAppPath(), " python script (*.py)"); if (fileName.size() != 0) { m_pyRecFile = new QFile(fileName); if (!m_pyRecFile->open(QIODevice::Append | QIODevice::Text)) return; m_pyRecording = new QTextStream(m_pyRecFile); foreach(View *v, m_views) *m_pyRecording << v->getName() << " = schnapps.getView(\"" << v->getName() << "\");" << endl; foreach(MapHandlerGen *m, m_maps) *m_pyRecording << m->getName() << " = schnapps.getMap(\"" << m->getName() << "\");" << endl; statusbar->showMessage(QString(" Append recording python in ") + fileName); } else { m_pyRecFile = NULL; m_pyRecording = NULL; statusbar->showMessage(QString(" Cancel recording python"), 2000); } } void SCHNApps::closeEvent(QCloseEvent *event) { DEBUG_EMIT("schnappsClosing"); emit(schnappsClosing()); QMainWindow::closeEvent(event); } } // namespace SCHNApps } // namespace CGoGN