GCC Code Coverage Report


Directory: ./
File: src/gui/osgwidget.cc
Date: 2024-12-20 15:53:58
Exec Total Coverage
Lines: 0 250 0.0%
Branches: 0 567 0.0%

Line Branch Exec Source
1 // Copyright (c) 2015-2018, LAAS-CNRS
2 // Authors: Joseph Mirabel (joseph.mirabel@laas.fr)
3 //
4 // This file is part of gepetto-viewer.
5 // gepetto-viewer is free software: you can redistribute it
6 // and/or modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation, either version
8 // 3 of the License, or (at your option) any later version.
9 //
10 // gepetto-viewer is distributed in the hope that it will be
11 // useful, but WITHOUT ANY WARRANTY; without even the implied warranty
12 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Lesser Public License for more details. You should have
14 // received a copy of the GNU Lesser General Public License along with
15 // gepetto-viewer. If not, see <http://www.gnu.org/licenses/>.
16
17 #include "gepetto/gui/osgwidget.hh"
18
19 #include <QAction>
20 #include <QDebug>
21 #include <QDockWidget>
22 #include <QFileDialog>
23 #include <QKeyEvent>
24 #include <QProcess>
25 #include <QTextBrowser>
26 #include <cassert>
27 #include <gepetto/gui/pick-handler.hh>
28 #include <osg/Camera>
29 #include <osg/DisplaySettings>
30 #include <osg/Geode>
31 #include <osg/Material>
32 #include <osg/Shape>
33 #include <osg/ShapeDrawable>
34 #include <osg/StateSet>
35 #include <osgGA/EventQueue>
36 #include <osgGA/KeySwitchMatrixManipulator>
37 #include <osgGA/TrackballManipulator>
38 #include <osgUtil/IntersectionVisitor>
39 #include <osgUtil/PolytopeIntersector>
40 #include <osgViewer/View>
41 #include <osgViewer/ViewerEventHandlers>
42 #include <stdexcept>
43 #include <vector>
44
45 #include "gepetto/gui/mainwindow.hh"
46 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
47 #include <QStandardPaths>
48 #endif
49 #include <gepetto/viewer/OSGManipulator/keyboard-manipulator.h>
50 #include <gepetto/viewer/macros.h>
51 #include <gepetto/viewer/urdf-parser.h>
52 #include <gepetto/viewer/window-manager.h>
53
54 #include <QToolBar>
55 #include <QVBoxLayout>
56 #include <QWheelEvent>
57 #include <gepetto/gui/action-search-bar.hh>
58 #include <gepetto/gui/selection-event.hh>
59 #include <gepetto/gui/windows-manager.hh>
60
61 namespace gepetto {
62 namespace gui {
63 OSGWidget::OSGWidget(WindowsManagerPtr_t wm, const std::string& name,
64 MainWindow* parent, Qt::WindowFlags f,
65 osgViewer::ViewerBase::ThreadingModel threadingModel)
66 : QWidget(parent, f),
67 graphicsWindow_(),
68 wsm_(wm),
69 pickHandler_(new PickHandler(this, wsm_)),
70 wid_(),
71 wm_(),
72 viewer_(new osgViewer::Viewer),
73 screenCapture_(),
74 tmpDirectory_(NULL),
75 toolBar_(new QToolBar(QString::fromStdString(name) + " tool bar")),
76 process_(new QProcess(this)),
77 showPOutput_(new QDialog(this, Qt::Dialog | Qt::WindowCloseButtonHint |
78 Qt::WindowMinMaxButtonsHint)),
79 pOutput_(new QTextBrowser()),
80 fullscreen_(new QWidget(NULL, Qt::Window | Qt::WindowStaysOnTopHint)) {
81 initGraphicsWindowsAndViewer(parent, name);
82 initToolBar();
83
84 wid_ = wm->createWindow(name, this, viewer_, graphicsWindow_.get());
85 wm_ = wsm_->getWindowManager(wid_);
86
87 viewer_->setThreadingModel(threadingModel);
88
89 osgQt::GLWidget* glWidget = graphicsWindow_->getGLWidget();
90 // glWidget->setForwardKeyEvents(true);
91 QVBoxLayout* hblayout = new QVBoxLayout(this);
92 hblayout->setContentsMargins(1, 1, 1, 1);
93 setLayout(hblayout);
94 hblayout->addWidget(toolBar_);
95 hblayout->addWidget(glWidget);
96 glWidget->setMinimumSize(50, 10);
97
98 // TODO Adding the properties here is problematic. They won't be
99 // shown in the GUI because the display is created before this code is run.
100 wm_->addProperty(viewer::BoolProperty::create(
101 "WindowFixedSize",
102 viewer::BoolProperty::getterFromMemberFunction(this,
103 &OSGWidget::isFixedSize),
104 viewer::BoolProperty::setterFromMemberFunction(
105 this, &OSGWidget::setFixedSize)));
106 wm_->addProperty(viewer::Vector2Property::create(
107 "WindowSize",
108 viewer::Vector2Property::getterFromMemberFunction(
109 wm_.get(), &viewer::WindowManager::getWindowDimension),
110 viewer::Vector2Property::Setter_t()));
111
112 nSuccessiveStaticFrames_ = 0;
113 connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
114 timer_.start(parent->settings_->refreshRate);
115
116 // Setup widgets to record movies.
117 process_->setProcessChannelMode(QProcess::MergedChannels);
118 connect(process_, SIGNAL(readyReadStandardOutput()),
119 SLOT(readyReadProcessOutput()));
120 showPOutput_->setModal(false);
121 showPOutput_->setLayout(new QHBoxLayout());
122 showPOutput_->layout()->addWidget(pOutput_);
123 }
124
125 OSGWidget::~OSGWidget() {
126 viewer_->setDone(true);
127 viewer_->removeEventHandler(pickHandler_);
128 pickHandler_ = NULL;
129 wm_.reset();
130 wsm_.reset();
131 delete fullscreen_;
132 }
133
134 void OSGWidget::paintEvent(QPaintEvent*) {
135 viewer::ScopedLock lock(wsm_->osgFrameMutex());
136 int refreshRate = MainWindow::instance()->settings_->refreshRate;
137 int sleepModeThr = int(6000. / refreshRate); // ~60 seconds.
138 if (wm_->frame()) {
139 // go back to active mode if necessary
140 if (nSuccessiveStaticFrames_ >= sleepModeThr)
141 timer_.setInterval(MainWindow::instance()->settings_->refreshRate);
142 nSuccessiveStaticFrames_ = 0;
143 } else if (nSuccessiveStaticFrames_ < sleepModeThr)
144 nSuccessiveStaticFrames_++;
145 else if (nSuccessiveStaticFrames_ == sleepModeThr) {
146 // go into sleep mode: check for updates every second only.
147 timer_.setInterval(1000);
148 nSuccessiveStaticFrames_++; // becomes > sleepModeThr
149 }
150 }
151
152 WindowsManager::WindowID OSGWidget::windowID() const { return wid_; }
153
154 WindowManagerPtr_t OSGWidget::window() const { return wm_; }
155
156 WindowsManagerPtr_t OSGWidget::osg() const { return wsm_; }
157
158 void OSGWidget::onHome() { viewer_->home(); }
159
160 void OSGWidget::addFloor() { wsm_->addFloor("hpp-gui/floor"); }
161
162 void OSGWidget::toggleCapture(bool active) {
163 MainWindow* main = MainWindow::instance();
164 if (active) {
165 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
166 if (tmpDirectory_ != NULL) {
167 qDebug() << "A temporary directory already exists: " << tmpDirectory_;
168 delete tmpDirectory_;
169 }
170 tmpDirectory_ =
171 new QTemporaryDir(QDir::temp().absoluteFilePath("gepetto-gui."));
172 if (!tmpDirectory_->isValid()) {
173 main->logError(
174 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
175 tmpDirectory_->errorString()
176 #else
177 "A temporary directory is not valid"
178 #endif
179 );
180 delete tmpDirectory_;
181 tmpDirectory_ = NULL;
182 recordMovie_->setChecked(false);
183 } else {
184 tmpDirectory_->setAutoRemove(false);
185 QString path =
186 #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
187 tmpDirectory_->filePath("img");
188 #else
189 tmpDirectory_->path() + "/img";
190 #endif
191 const char* ext = "jpeg";
192 osg()->startCapture(windowID(), path.toLocal8Bit().data(), ext);
193 main->log("Saving images to " + path + "_<number>." + ext);
194 }
195 #else
196 QDir tmp("/tmp");
197 tmp.mkpath("gepetto-gui/record");
198 tmp.cd("gepetto-gui/record");
199 foreach (QString f,
200 tmp.entryList(QStringList() << "img_0_*.jpeg", QDir::Files))
201 tmp.remove(f);
202 QString path = tmp.absoluteFilePath("img");
203 const char* ext = "jpeg";
204 osg()->startCapture(windowID(), path.toLocal8Bit().data(), ext);
205 main->log("Saving images to " + path + "_*." + ext);
206 #endif
207 } else {
208 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
209 if (tmpDirectory_ == NULL) return;
210 QString input =
211 #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
212 tmpDirectory_->filePath("img_%d.jpeg");
213 #else
214 tmpDirectory_->path() + "/img_%d.jpeg";
215 #endif
216 #else
217 QString input = "/tmp/gepetto-gui/record/img_0_%d.jpeg";
218 #endif
219 osg()->stopCapture(windowID());
220 QString outputFile =
221 QFileDialog::getSaveFileName(this, tr("Save video to"), "untitled.mp4");
222 if (!outputFile.isNull()) {
223 if (QFile::exists(outputFile)) QFile::remove(outputFile);
224 QString ffmpeg = main->settings_->ffmpeg;
225
226 QStringList args;
227 args << main->settings_->ffmpegInputOptions << "-i" << input
228 << main->settings_->ffmpegOutputOptions << outputFile;
229 qDebug() << args;
230
231 showPOutput_->setWindowTitle(ffmpeg + " " + args.join(" "));
232 pOutput_->clear();
233 showPOutput_->resize(main->size() / 2);
234 showPOutput_->show();
235 process_->start(ffmpeg, args);
236 bool started = process_->waitForStarted(-1);
237 if (!started) {
238 showPOutput_->hide();
239 switch (process_->error()) {
240 case QProcess::FailedToStart:
241 main->logError(
242 "Failed to start " + ffmpeg +
243 ". Either it is missing, "
244 "or you may have insufficient permissions to invoke it.\n");
245 break;
246 case QProcess::Crashed:
247 main->logError("" + ffmpeg +
248 " crashed some time after starting successfully.");
249 break;
250 case QProcess::Timedout:
251 case QProcess::WriteError:
252 case QProcess::ReadError:
253 case QProcess::UnknownError:
254 main->logError("An unknown error made " + ffmpeg +
255 " stopped before "
256 "finishing.");
257 break;
258 }
259 main->logError("You can manually run\n" + ffmpeg + " " +
260 args.join(" "));
261 }
262 }
263 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
264 delete tmpDirectory_;
265 tmpDirectory_ = NULL;
266 #endif
267 }
268 }
269
270 void OSGWidget::captureFrame() {
271 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
272 QString pathname =
273 QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
274 #else
275 QString pathname = QDir::home().filePath("Pictures");
276 #endif
277 if (pathname.isEmpty()) {
278 qDebug() << "Unable to get the directory where to write images";
279 return;
280 }
281 QDir path(pathname);
282 if (!path.mkpath(".")) {
283 qDebug() << "Unable to create directory" << pathname;
284 return;
285 }
286 QString filename(path.filePath(
287 QDateTime::currentDateTime().toString("yyyy-MM-dd.hh-mm-ss'.png'")));
288 captureFrame(filename.toStdString());
289 }
290
291 void OSGWidget::captureFrame(const std::string& filename) {
292 viewer::ScopedLock lock(wsm_->osgFrameMutex());
293 wm_->captureFrame(filename);
294 }
295
296 bool OSGWidget::startCapture(const std::string& filename,
297 const std::string& extension) {
298 viewer::ScopedLock lock(wsm_->osgFrameMutex());
299 wm_->startCapture(filename, extension);
300 return true;
301 }
302
303 bool OSGWidget::stopCapture() {
304 viewer::ScopedLock lock(wsm_->osgFrameMutex());
305 wm_->stopCapture();
306 return true;
307 }
308
309 bool OSGWidget::isFixedSize() const {
310 try {
311 return wm_->property("WindowSize")->hasWriteAccess();
312 } catch (const std::invalid_argument&) {
313 return false;
314 }
315 }
316
317 void OSGWidget::setFixedSize(bool fixedSize) {
318 try {
319 viewer::Property& size(*wm_->property("WindowSize"));
320 if (size.hasWriteAccess() == fixedSize) return;
321
322 // Cast to Vector2Property
323 viewer::Vector2Property& vsize(
324 dynamic_cast<viewer::Vector2Property&>(size));
325 osgQt::GLWidget* glWidget = graphicsWindow_->getGLWidget();
326 if (fixedSize) {
327 glWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
328 // Add write access
329 vsize.setter(viewer::Vector2Property::Setter_t(
330 viewer::Vector2Property::setterFromMemberFunction(
331 this, &OSGWidget::setWindowDimension)));
332 } else {
333 glWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
334 // Remove write access
335 vsize.setter(viewer::Vector2Property::Setter_t());
336 }
337 } catch (const std::invalid_argument&) {
338 return;
339 } catch (const std::bad_cast&) {
340 return;
341 }
342 }
343
344 void OSGWidget::setWindowDimension(const osgVector2& size) {
345 osgQt::GLWidget* glWidget = graphicsWindow_->getGLWidget();
346 if (isFixedSize()) {
347 glWidget->resize((int)size[0], (int)size[1]);
348 glWidget->setMinimumSize((int)size[0], (int)size[1]);
349 } else
350 glWidget->setMinimumSize(50, 10);
351 }
352
353 void OSGWidget::readyReadProcessOutput() {
354 pOutput_->append(process_->readAll());
355 }
356
357 QIcon iconFromTheme(const QString& name) {
358 QIcon icon;
359 if (QIcon::hasThemeIcon(name)) {
360 icon = QIcon::fromTheme(name);
361 } else {
362 icon.addFile(QString::fromUtf8(""), QSize(), QIcon::Normal, QIcon::Off);
363 }
364 return icon;
365 }
366
367 void OSGWidget::initToolBar() {
368 toolBar_
369 ->addAction(iconFromTheme("zoom-fit-best"), "Zoom to fit", this,
370 SLOT(onHome()))
371 ->setToolTip("Zoom to fit");
372
373 QIcon icon;
374 icon.addFile(QString::fromUtf8(":/icons/floor.png"), QSize(), QIcon::Normal,
375 QIcon::Off);
376 QAction* addFloor =
377 toolBar_->addAction(icon, "Add floor", this, SLOT(addFloor()));
378 addFloor->setToolTip("Add a floor");
379 MainWindow* main = MainWindow::instance();
380 main->actionSearchBar()->addAction(addFloor);
381
382 toolBar_
383 ->addAction(iconFromTheme("insert-image"), "Take snapshot", this,
384 SLOT(captureFrame()))
385 ->setToolTip("Take a snapshot");
386
387 recordMovie_ =
388 toolBar_->addAction(iconFromTheme("media-record"), "Record movie");
389 connect(recordMovie_, SIGNAL(triggered(bool)), SLOT(toggleCapture(bool)),
390 Qt::QueuedConnection);
391 recordMovie_->setCheckable(true);
392 recordMovie_->setToolTip(
393 "Record the central widget as a sequence of images. You can find the "
394 "images in /tmp/gepetto-gui/record/img_%d.jpeg");
395
396 QAction* toggleFullscreen = new QAction(iconFromTheme("view-fullscreen"),
397 "Toggle fullscreen mode", this);
398 toggleFullscreen->setShortcut(Qt::SHIFT | Qt::Key_F);
399 toggleFullscreen->setCheckable(true);
400 toggleFullscreen->setChecked(false);
401 connect(toggleFullscreen, SIGNAL(toggled(bool)),
402 SLOT(toggleFullscreenMode(bool)));
403 toolBar_->addAction(toggleFullscreen);
404
405 fullscreen_->setLayout(new QVBoxLayout);
406 fullscreen_->layout()->setContentsMargins(0, 0, 0, 0);
407 }
408
409 void OSGWidget::initGraphicsWindowsAndViewer(MainWindow* parent,
410 const std::string& name) {
411 osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
412 osg::ref_ptr<osg::GraphicsContext::Traits> traits_ptr(
413 new osg::GraphicsContext::Traits(ds));
414 traits_ptr->windowName = name;
415 traits_ptr->x = this->x();
416 traits_ptr->y = this->y();
417 traits_ptr->width = this->width();
418 traits_ptr->height = this->height();
419 traits_ptr->windowDecoration = false;
420 traits_ptr->doubleBuffer = true;
421 traits_ptr->vsync = true;
422 // traits_ptr->sharedContext = 0;
423
424 graphicsWindow_ = new osgQt::GraphicsWindowQt(traits_ptr);
425
426 osg::Camera* camera = viewer_->getCamera();
427 camera->setGraphicsContext(graphicsWindow_);
428
429 camera->setClearColor(osg::Vec4(0.2f, 0.2f, 0.6f, 1.0f));
430 camera->setViewport(
431 new osg::Viewport(0, 0, traits_ptr->width, traits_ptr->height));
432 camera->setProjectionMatrixAsPerspective(
433 30.0f,
434 static_cast<double>(traits_ptr->width) /
435 static_cast<double>(traits_ptr->height),
436 1.0f, 10000.0f);
437
438 viewer_->setKeyEventSetsDone(0);
439
440 // Manipulators
441 osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator =
442 new osgGA::KeySwitchMatrixManipulator;
443 keyswitchManipulator->addMatrixManipulator('1', "Trackball",
444 new osgGA::TrackballManipulator());
445 keyswitchManipulator->addMatrixManipulator(
446 '2', "First person", new ::osgGA::KeyboardManipulator(graphicsWindow_));
447 keyswitchManipulator->selectMatrixManipulator(0);
448 viewer_->setCameraManipulator(keyswitchManipulator.get());
449
450 // Event handlers
451 screenCapture_ = new osgViewer::ScreenCaptureHandler(
452 new osgViewer::ScreenCaptureHandler::WriteToFile(
453 parent->settings_->captureDirectory + "/" +
454 parent->settings_->captureFilename,
455 parent->settings_->captureExtension),
456 1);
457 viewer_->addEventHandler(screenCapture_);
458 viewer_->addEventHandler(new osgViewer::HelpHandler);
459 viewer_->addEventHandler(pickHandler_);
460 viewer_->addEventHandler(new osgViewer::StatsHandler);
461 }
462
463 void OSGWidget::toggleFullscreenMode(bool fullscreenOn) {
464 if (!isVisible()) return;
465 if (fullscreenOn) {
466 QDockWidget* dockOSG = qobject_cast<QDockWidget*>(this->parentWidget());
467 if (dockOSG) {
468 normal_ = this->parentWidget();
469 fullscreen_->layout()->addWidget(this);
470 fullscreen_->showFullScreen();
471 }
472 } else {
473 QDockWidget* dockOSG = qobject_cast<QDockWidget*>(normal_);
474 if (dockOSG) {
475 fullscreen_->hide();
476 dockOSG->setWidget(this);
477 }
478 }
479 }
480 } // namespace gui
481 } // namespace gepetto
482