void PointViewerMainWindow::openShaderFile() { QString shaderFileName = QFileDialog::getOpenFileName( this, tr("Open OpenGL shader in displaz format"), m_currShaderFileName, tr("OpenGL shader files (*.glsl);;All files(*)") ); if (shaderFileName.isNull()) return; openShaderFile(shaderFileName); }
void PointViewerMainWindow::handleMessage(QByteArray message) { QList<QByteArray> commandTokens = message.split('\n'); if (commandTokens.empty()) return; if (commandTokens[0] == "OPEN_FILES") { QList<QByteArray> flags = commandTokens[1].split('\0'); bool replaceLabel = flags.contains("REPLACE_LABEL"); bool deleteAfterLoad = flags.contains("DELETE_AFTER_LOAD"); bool mutateExisting = flags.contains("MUTATE_EXISTING"); for (int i = 2; i < commandTokens.size(); ++i) { QList<QByteArray> pathAndLabel = commandTokens[i].split('\0'); if (pathAndLabel.size() != 2) { g_logger.error("Unrecognized OPEN_FILES token: %s", QString(commandTokens[i])); continue; } FileLoadInfo loadInfo(pathAndLabel[0], pathAndLabel[1], replaceLabel); loadInfo.deleteAfterLoad = deleteAfterLoad; loadInfo.mutateExisting = mutateExisting; m_fileLoader->loadFile(loadInfo); } } else if (commandTokens[0] == "CLEAR_FILES") { m_geometries->clear(); } else if (commandTokens[0] == "UNLOAD_FILES") { QString regex_str = commandTokens[1]; QRegExp regex(regex_str, Qt::CaseSensitive, QRegExp::WildcardUnix); if (!regex.isValid()) { g_logger.error("Invalid pattern in -unload command: '%s': %s", regex_str, regex.errorString()); return; } m_geometries->unloadFiles(regex); m_pointView->removeAnnotations(regex); } else if (commandTokens[0] == "SET_VIEW_LABEL") { QString regex_str = commandTokens[1]; QRegExp regex(regex_str, Qt::CaseSensitive, QRegExp::FixedString); if (!regex.isValid()) { g_logger.error("Invalid pattern in -unload command: '%s': %s", regex_str, regex.errorString()); return; } QModelIndex index = m_geometries->findLabel(regex); if (index.isValid()) m_pointView->centerOnGeometry(index); } else if (commandTokens[0] == "ANNOTATE") { if (commandTokens.size() - 1 != 5) { tfm::format(std::cerr, "Expected five arguments, got %d\n", commandTokens.size() - 1); return; } QString label = commandTokens[1]; QString text = commandTokens[2]; bool xOk = false, yOk = false, zOk = false; double x = commandTokens[3].toDouble(&xOk); double y = commandTokens[4].toDouble(&yOk); double z = commandTokens[5].toDouble(&zOk); if (!zOk || !yOk || !zOk) { std::cerr << "Could not parse XYZ coordinates for position\n"; return; } m_pointView->addAnnotation(label, text, Imath::V3d(x, y, z)); } else if (commandTokens[0] == "SET_VIEW_POSITION") { if (commandTokens.size()-1 != 3) { tfm::format(std::cerr, "Expected three coordinates, got %d\n", commandTokens.size()-1); return; } bool xOk = false, yOk = false, zOk = false; double x = commandTokens[1].toDouble(&xOk); double y = commandTokens[2].toDouble(&yOk); double z = commandTokens[3].toDouble(&zOk); if (!zOk || !yOk || !zOk) { std::cerr << "Could not parse XYZ coordinates for position\n"; return; } m_pointView->setExplicitCursorPos(Imath::V3d(x, y, z)); } else if (commandTokens[0] == "SET_VIEW_ANGLES") { if (commandTokens.size()-1 != 3) { tfm::format(std::cerr, "Expected three view angles, got %d\n", commandTokens.size()-1); return; } bool yawOk = false, pitchOk = false, rollOk = false; double yaw = commandTokens[1].toDouble(&yawOk); double pitch = commandTokens[2].toDouble(&pitchOk); double roll = commandTokens[3].toDouble(&rollOk); if (!yawOk || !pitchOk || !rollOk) { std::cerr << "Could not parse Euler angles for view\n"; return; } m_pointView->camera().setRotation( QQuaternion::fromAxisAndAngle(0,0,1, roll) * QQuaternion::fromAxisAndAngle(1,0,0, pitch-90) * QQuaternion::fromAxisAndAngle(0,0,1, yaw) ); } else if (commandTokens[0] == "SET_VIEW_ROTATION") { if (commandTokens.size()-1 != 9) { tfm::format(std::cerr, "Expected 9 rotation matrix components, got %d\n", commandTokens.size()-1); return; } # ifdef DISPLAZ_USE_QT4 qreal rot[9] = {0}; # else float rot[9] = {0}; # endif for (int i = 0; i < 9; ++i) { bool ok = true; rot[i] = commandTokens[i+1].toDouble(&ok); if(!ok) { tfm::format(std::cerr, "badly formatted view matrix message:\n%s", message.constData()); return; } } m_pointView->camera().setRotation(QMatrix3x3(rot)); } else if (commandTokens[0] == "SET_VIEW_RADIUS") { bool ok = false; double viewRadius = commandTokens[1].toDouble(&ok); if (!ok) { std::cerr << "Could not parse view radius"; return; } m_pointView->camera().setEyeToCenterDistance(viewRadius); } else if (commandTokens[0] == "QUERY_CURSOR") { // Yuck! IpcChannel* channel = dynamic_cast<IpcChannel*>(sender()); if (!channel) { qWarning() << "Signalling object not a IpcChannel!\n"; return; } V3d p = m_pointView->cursorPos(); std::string response = tfm::format("%.15g %.15g %.15g", p.x, p.y, p.z); channel->sendMessage(QByteArray(response.data(), (int)response.size())); } else if (commandTokens[0] == "QUIT") { close(); } else if (commandTokens[0] == "SET_MAX_POINT_COUNT") { m_maxPointCount = commandTokens[1].toLongLong(); } else if (commandTokens[0] == "OPEN_SHADER") { openShaderFile(commandTokens[1]); } else if (commandTokens[0] == "NOTIFY") { if (commandTokens.size() < 3) { g_logger.error("Could not parse NOTIFY message: %s", QString::fromUtf8(message)); return; } QString spec = QString::fromUtf8(commandTokens[1]); QList<QString> specList = spec.split(':'); if (specList[0].toLower() != "log") { g_logger.error("Could not parse NOTIFY spec: %s", spec); return; } Logger::LogLevel level = Logger::Info; if (specList.size() > 1) level = Logger::parseLogLevel(specList[1].toLower().toStdString()); // Ugh, reassemble message from multiple lines. Need a better // transport serialization! QByteArray message; for (int i = 2; i < commandTokens.size(); ++i) { if (i > 2) message += "\n"; message += commandTokens[i]; } g_logger.log(level, "%s", tfm::makeFormatList(message.constData())); } else if(commandTokens[0] == "HOOK") { IpcChannel* channel = dynamic_cast<IpcChannel*>(sender()); if (!channel) { qWarning() << "Signalling object not a IpcChannel!\n"; return; } for(int i=1; i<commandTokens.count(); i+=2) { HookFormatter* formatter = new HookFormatter(this, commandTokens[i], commandTokens[i+1], channel); m_hookManager->connectHook(commandTokens[i], formatter); } } else { g_logger.error("Unkown remote message:\n%s", QString::fromUtf8(message)); } }
PointViewerMainWindow::PointViewerMainWindow(const QGLFormat& format) : m_progressBar(0), m_pointView(0), m_shaderEditor(0), m_helpDialog(0), m_logTextView(0), m_maxPointCount(200*1000*1000), // 200 million m_geometries(0), m_ipcServer(0), m_hookManager(0) { #ifndef _WIN32 setWindowIcon(QIcon(":resource/icons/hicolor/256x256/apps/displaz.png")); #else // On windows, application icon is set via windows resource file #endif setWindowTitle("Displaz"); setAcceptDrops(true); m_helpDialog = new HelpDialog(this); m_geometries = new GeometryCollection(this); connect(m_geometries, SIGNAL(layoutChanged()), this, SLOT(updateTitle())); connect(m_geometries, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateTitle())); connect(m_geometries, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateTitle())); connect(m_geometries, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateTitle())); //-------------------------------------------------- // Set up file loader in a separate thread // // Some subtleties regarding qt thread usage are discussed here: // http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation // // Main point: each QObject has a thread affinity which determines which // thread its slots will execute on, when called via a connected signal. QThread* loaderThread = new QThread(); m_fileLoader = new FileLoader(m_maxPointCount); m_fileLoader->moveToThread(loaderThread); connect(loaderThread, SIGNAL(finished()), m_fileLoader, SLOT(deleteLater())); connect(loaderThread, SIGNAL(finished()), loaderThread, SLOT(deleteLater())); //connect(m_fileLoader, SIGNAL(finished()), this, SIGNAL(fileLoadFinished())); connect(m_fileLoader, SIGNAL(geometryLoaded(std::shared_ptr<Geometry>, bool, bool)), m_geometries, SLOT(addGeometry(std::shared_ptr<Geometry>, bool, bool))); connect(m_fileLoader, SIGNAL(geometryMutatorLoaded(std::shared_ptr<GeometryMutator>)), m_geometries, SLOT(mutateGeometry(std::shared_ptr<GeometryMutator>))); loaderThread->start(); //-------------------------------------------------- // Menus menuBar()->setNativeMenuBar(false); // OS X doesn't activate the native menu bar under Qt5 // File menu QMenu* fileMenu = menuBar()->addMenu(tr("&File")); QAction* openAct = fileMenu->addAction(tr("&Open")); openAct->setToolTip(tr("Open a data set")); openAct->setShortcuts(QKeySequence::Open); connect(openAct, SIGNAL(triggered()), this, SLOT(openFiles())); QAction* addAct = fileMenu->addAction(tr("&Add")); addAct->setToolTip(tr("Add a data set")); connect(addAct, SIGNAL(triggered()), this, SLOT(addFiles())); QAction* reloadAct = fileMenu->addAction(tr("&Reload")); reloadAct->setStatusTip(tr("Reload point files from disk")); reloadAct->setShortcut(Qt::Key_F5); connect(reloadAct, SIGNAL(triggered()), this, SLOT(reloadFiles())); fileMenu->addSeparator(); QAction* screenShotAct = fileMenu->addAction(tr("Scree&nshot")); screenShotAct->setStatusTip(tr("Save screen shot of 3D window")); screenShotAct->setShortcut(Qt::Key_F9); connect(screenShotAct, SIGNAL(triggered()), this, SLOT(screenShot())); fileMenu->addSeparator(); QAction* quitAct = fileMenu->addAction(tr("&Quit")); quitAct->setStatusTip(tr("Exit the application")); quitAct->setShortcuts(QKeySequence::Quit); connect(quitAct, SIGNAL(triggered()), this, SLOT(close())); // View menu QMenu* viewMenu = menuBar()->addMenu(tr("&View")); QAction* trackballMode = viewMenu->addAction(tr("Use &Trackball camera")); trackballMode->setCheckable(true); trackballMode->setChecked(false); // Background sub-menu QMenu* backMenu = viewMenu->addMenu(tr("Set &Background")); QSignalMapper* mapper = new QSignalMapper(this); // Selectable backgrounds (svg_names from SVG standard - see QColor docs) const char* backgroundNames[] = {/* "Display Name", "svg_name", */ "Default", "#3C3232", "Black", "black", "Dark Grey", "dimgrey", "Slate Grey", "#858C93", "Light Grey", "lightgrey", "White", "white" }; for(size_t i = 0; i < sizeof(backgroundNames)/sizeof(const char*); i+=2) { QAction* backgroundAct = backMenu->addAction(tr(backgroundNames[i])); QPixmap pixmap(50,50); QString colName = backgroundNames[i+1]; pixmap.fill(QColor(colName)); QIcon icon(pixmap); backgroundAct->setIcon(icon); mapper->setMapping(backgroundAct, colName); connect(backgroundAct, SIGNAL(triggered()), mapper, SLOT(map())); } connect(mapper, SIGNAL(mapped(QString)), this, SLOT(setBackground(QString))); backMenu->addSeparator(); QAction* backgroundCustom = backMenu->addAction(tr("&Custom")); connect(backgroundCustom, SIGNAL(triggered()), this, SLOT(chooseBackground())); // Check boxes for drawing various scene elements by category viewMenu->addSeparator(); QAction* drawBoundingBoxes = viewMenu->addAction(tr("Draw Bounding bo&xes")); drawBoundingBoxes->setCheckable(true); drawBoundingBoxes->setChecked(false); QAction* drawCursor = viewMenu->addAction(tr("Draw 3D &Cursor")); drawCursor->setCheckable(true); drawCursor->setChecked(true); QAction* drawAxes = viewMenu->addAction(tr("Draw &Axes")); drawAxes->setCheckable(true); drawAxes->setChecked(true); QAction* drawGrid = viewMenu->addAction(tr("Draw &Grid")); drawGrid->setCheckable(true); drawGrid->setChecked(false); QAction* drawAnnotations = viewMenu->addAction(tr("Draw A&nnotations")); drawAnnotations->setCheckable(true); drawAnnotations->setChecked(true); // Shader menu QMenu* shaderMenu = menuBar()->addMenu(tr("&Shader")); QAction* openShaderAct = shaderMenu->addAction(tr("&Open")); openShaderAct->setToolTip(tr("Open a shader file")); connect(openShaderAct, SIGNAL(triggered()), this, SLOT(openShaderFile())); QAction* editShaderAct = shaderMenu->addAction(tr("&Edit")); editShaderAct->setToolTip(tr("Open shader editor window")); QAction* saveShaderAct = shaderMenu->addAction(tr("&Save")); saveShaderAct->setToolTip(tr("Save current shader file")); connect(saveShaderAct, SIGNAL(triggered()), this, SLOT(saveShaderFile())); shaderMenu->addSeparator(); // Help menu QMenu* helpMenu = menuBar()->addMenu(tr("&Help")); QAction* helpAct = helpMenu->addAction(tr("User &Guide")); connect(helpAct, SIGNAL(triggered()), this, SLOT(helpDialog())); helpMenu->addSeparator(); QAction* aboutAct = helpMenu->addAction(tr("&About")); connect(aboutAct, SIGNAL(triggered()), this, SLOT(aboutDialog())); //-------------------------------------------------- // Point viewer m_pointView = new View3D(m_geometries, format, this); setCentralWidget(m_pointView); connect(drawBoundingBoxes, SIGNAL(triggered()), m_pointView, SLOT(toggleDrawBoundingBoxes())); connect(drawCursor, SIGNAL(triggered()), m_pointView, SLOT(toggleDrawCursor())); connect(drawAxes, SIGNAL(triggered()), m_pointView, SLOT(toggleDrawAxes())); connect(drawGrid, SIGNAL(triggered()), m_pointView, SLOT(toggleDrawGrid())); connect(drawAnnotations, SIGNAL(triggered()), m_pointView, SLOT(toggleDrawAnnotations())); connect(trackballMode, SIGNAL(triggered()), m_pointView, SLOT(toggleCameraMode())); connect(m_geometries, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(geometryRowsInserted(QModelIndex,int,int))); //-------------------------------------------------- // Docked widgets // Shader parameters UI QDockWidget* shaderParamsDock = new QDockWidget(tr("Shader Parameters"), this); shaderParamsDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable); QWidget* shaderParamsUI = new QWidget(shaderParamsDock); shaderParamsDock->setWidget(shaderParamsUI); m_pointView->setShaderParamsUIWidget(shaderParamsUI); // Shader editor UI QDockWidget* shaderEditorDock = new QDockWidget(tr("Shader Editor"), this); shaderEditorDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable); QWidget* shaderEditorUI = new QWidget(shaderEditorDock); m_shaderEditor = new ShaderEditor(shaderEditorUI); QGridLayout* shaderEditorLayout = new QGridLayout(shaderEditorUI); shaderEditorLayout->setContentsMargins(2,2,2,2); shaderEditorLayout->addWidget(m_shaderEditor, 0, 0, 1, 1); connect(editShaderAct, SIGNAL(triggered()), shaderEditorDock, SLOT(show())); shaderEditorDock->setWidget(shaderEditorUI); shaderMenu->addAction(m_shaderEditor->compileAction()); connect(m_shaderEditor->compileAction(), SIGNAL(triggered()), this, SLOT(compileShaderFile())); // TODO: check if this is needed - test shader update functionality //connect(m_shaderEditor, SIGNAL(sendShader(QString)), // &m_pointView->shaderProgram(), SLOT(setShader(QString))); // Log viewer UI QDockWidget* logDock = new QDockWidget(tr("Log"), this); logDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable); QWidget* logUI = new QWidget(logDock); m_logTextView = new LogViewer(logUI); m_logTextView->setReadOnly(true); m_logTextView->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); m_logTextView->connectLogger(&g_logger); // connect to global logger m_progressBar = new QProgressBar(logUI); m_progressBar->setRange(0,100); m_progressBar->setValue(0); m_progressBar->hide(); connect(m_fileLoader, SIGNAL(loadStepStarted(QString)), this, SLOT(setProgressBarText(QString))); connect(m_fileLoader, SIGNAL(loadProgress(int)), m_progressBar, SLOT(setValue(int))); connect(m_fileLoader, SIGNAL(resetProgress()), m_progressBar, SLOT(hide())); QVBoxLayout* logUILayout = new QVBoxLayout(logUI); //logUILayout->setContentsMargins(2,2,2,2); logUILayout->addWidget(m_logTextView); logUILayout->addWidget(m_progressBar); //m_logTextView->setLineWrapMode(QPlainTextEdit::NoWrap); logDock->setWidget(logUI); // Data set list UI QDockWidget* dataSetDock = new QDockWidget(tr("Data Sets"), this); dataSetDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable); DataSetUI* dataSetUI = new DataSetUI(this); dataSetDock->setWidget(dataSetUI); QAbstractItemView* dataSetOverview = dataSetUI->view(); dataSetOverview->setModel(m_geometries); connect(dataSetOverview, SIGNAL(doubleClicked(const QModelIndex&)), m_pointView, SLOT(centerOnGeometry(const QModelIndex&))); m_pointView->setSelectionModel(dataSetOverview->selectionModel()); // Set up docked widgets addDockWidget(Qt::RightDockWidgetArea, shaderParamsDock); addDockWidget(Qt::LeftDockWidgetArea, shaderEditorDock); addDockWidget(Qt::RightDockWidgetArea, logDock); addDockWidget(Qt::RightDockWidgetArea, dataSetDock); tabifyDockWidget(logDock, dataSetDock); logDock->raise(); shaderEditorDock->setVisible(false); // Add dock widget toggles to view menu viewMenu->addSeparator(); viewMenu->addAction(shaderParamsDock->toggleViewAction()); viewMenu->addAction(logDock->toggleViewAction()); viewMenu->addAction(dataSetDock->toggleViewAction()); // Create custom hook events from CLI at runtime m_hookManager = new HookManager(this); }
void PointViewerMainWindow::handleMessage(QByteArray message) { QList<QByteArray> commandTokens = message.split('\n'); if (commandTokens.empty()) return; if (commandTokens[0] == "OPEN_FILES") { QList<QByteArray> flags = commandTokens[1].split('\0'); if (flags.contains("CLEAR")) m_geometries->clear(); bool rmTemp = flags.contains("RMTEMP"); for (int i = 2; i < commandTokens.size(); ++i) { QList<QByteArray> pathAndLabel = commandTokens[i].split('\0'); if (pathAndLabel.size() != 2) { g_logger.error("Unrecognized OPEN_FILES token: %s", QString(commandTokens[i])); continue; } m_fileLoader->loadFile(FileLoadInfo(pathAndLabel[0], pathAndLabel[1], rmTemp)); } } else if (commandTokens[0] == "CLEAR_FILES") { m_geometries->clear(); } else if (commandTokens[0] == "UNLOAD_FILES") { QString regex_str = commandTokens[1]; QRegExp regex(regex_str, Qt::CaseSensitive, QRegExp::WildcardUnix); if (!regex.isValid()) { g_logger.error("Invalid pattern in -unload command: '%s': %s", regex_str, regex.errorString()); return; } m_geometries->unloadFiles(regex); } else if (commandTokens[0] == "SET_VIEW_POSITION") { if (commandTokens.size()-1 != 3) { tfm::format(std::cerr, "Expected three coordinates, got %d\n", commandTokens.size()-1); return; } bool xOk = false, yOk = false, zOk = false; double x = commandTokens[1].toDouble(&xOk); double y = commandTokens[2].toDouble(&yOk); double z = commandTokens[3].toDouble(&zOk); if (!zOk || !yOk || !zOk) { std::cerr << "Could not parse XYZ coordinates for position\n"; return; } m_pointView->setExplicitCursorPos(Imath::V3d(x, y, z)); } else if (commandTokens[0] == "SET_VIEW_ANGLES") { if (commandTokens.size()-1 != 3) { tfm::format(std::cerr, "Expected three view angles, got %d\n", commandTokens.size()-1); return; } bool yawOk = false, pitchOk = false, rollOk = false; double yaw = commandTokens[1].toDouble(&yawOk); double pitch = commandTokens[2].toDouble(&pitchOk); double roll = commandTokens[3].toDouble(&rollOk); if (!yawOk || !pitchOk || !rollOk) { std::cerr << "Could not parse Euler angles for view\n"; return; } m_pointView->camera().setRotation( QQuaternion::fromAxisAndAngle(0,0,1, roll) * QQuaternion::fromAxisAndAngle(1,0,0, pitch-90) * QQuaternion::fromAxisAndAngle(0,0,1, yaw) ); } else if (commandTokens[0] == "SET_VIEW_RADIUS") { bool ok = false; double viewRadius = commandTokens[1].toDouble(&ok); if (!ok) { std::cerr << "Could not parse view radius"; return; } m_pointView->camera().setEyeToCenterDistance(viewRadius); } else if (commandTokens[0] == "QUERY_CURSOR") { // Yuck! IpcChannel* channel = dynamic_cast<IpcChannel*>(sender()); if (!channel) { qWarning() << "Signalling object not a IpcChannel!\n"; return; } V3d p = m_pointView->cursorPos(); std::string response = tfm::format("%.15g %.15g %.15g", p.x, p.y, p.z); channel->sendMessage(QByteArray(response.data(), (int)response.size())); } else if (commandTokens[0] == "QUIT") { close(); } else if (commandTokens[0] == "SET_MAX_POINT_COUNT") { m_maxPointCount = commandTokens[1].toLongLong(); } else if (commandTokens[0] == "OPEN_SHADER") { openShaderFile(commandTokens[1]); } else { g_logger.error("Unkown remote message:\n%s", QString::fromUtf8(message)); } }