GCC Code Coverage Report


Directory: ./
File: src/gui/osgwidget.cc
Date: 2025-04-14 11:18:00
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