GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/gui/osgwidget.cc Lines: 0 240 0.0 %
Date: 2024-04-14 11:13:22 Branches: 0 558 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