ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ccCloudLayersDlg.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "../include/ccCloudLayersDlg.h"
9 
10 #include "../include/ccColorStyledDelegate.h"
11 #include "../include/ccMouseCircle.h"
12 
13 // QT
14 #include <QColorDialog>
15 #include <QKeyEvent>
16 #include <QMessageBox>
17 #include <QSettings>
18 #include <QSortFilterProxyModel>
19 #include <QWidget>
20 
21 // CC
22 #include <ecvPointCloud.h>
23 
25  : ccOverlayDialog(parent),
27  m_app(app),
28  m_helper(nullptr),
29  m_mouseCircle(nullptr) {
30  setupUi(this);
31 
32  setWindowTitle(QString("Cloud layers plugin"));
33 
34  // allow resize and move window
35  setWindowFlags(Qt::Tool |
36  Qt::CustomizeWindowHint /* | Qt::WindowTitleHint*/);
37 
38  // set model to tableView
39  initTableView();
40 
41  // connect buttons
42  connect(pbAdd, &QPushButton::clicked, this, &ccCloudLayersDlg::addClicked);
43  connect(pbDelete, &QPushButton::clicked, this,
44  &ccCloudLayersDlg::deleteClicked);
45  connect(pbStart, &QPushButton::clicked, this,
46  &ccCloudLayersDlg::startClicked);
47  connect(pbPause, &QPushButton::clicked, this,
48  &ccCloudLayersDlg::pauseClicked);
49  connect(pbApply, &QPushButton::clicked, this,
50  &ccCloudLayersDlg::applyClicked);
51  connect(pbClose, &QPushButton::clicked, this,
52  &ccCloudLayersDlg::closeClicked);
53 
54  // connect comboboxes
55  connect(cbScalarField, qOverload<int>(&QComboBox::currentIndexChanged),
56  this, &ccCloudLayersDlg::scalarFieldIndexChanged);
57  connect(cbInput, qOverload<int>(&QComboBox::currentIndexChanged), this,
58  &ccCloudLayersDlg::inputClassIndexChanged);
59  connect(cbOutput, qOverload<int>(&QComboBox::currentIndexChanged), this,
60  &ccCloudLayersDlg::outputClassIndexChanged);
61 
62  // color picker
63  connect(tableView, &QTableView::doubleClicked, this,
64  &ccCloudLayersDlg::tableViewDoubleClicked);
65 
66  // asprs model changed signals
67  connect(&m_asprsModel, &ccAsprsModel::codeChanged, this,
68  &ccCloudLayersDlg::codeChanged);
69  connect(&m_asprsModel, &ccAsprsModel::colorChanged, this,
70  &ccCloudLayersDlg::colorChanged);
71 
72  m_presets.append(QString("All Points"));
73  m_presets.append(QString("Visible Points"));
74 
75  m_mouseCircle = new ccMouseCircle(m_app, m_app->getActiveWindow());
76  m_mouseCircle->setVisible(false);
77 }
78 
80  setPointCloud(nullptr);
81 
82  if (m_mouseCircle) {
83  delete m_mouseCircle;
84  m_mouseCircle = nullptr;
85  }
86 }
87 
88 void ccCloudLayersDlg::reject() {
89  if (m_helper && m_helper->hasChanges()) {
90  if (QMessageBox::question(
91  m_associatedWin, "Cloud layers plugin",
92  "The cloud has been modified, are you sure you want exit?",
93  QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) {
94  return;
95  }
96  }
97 
98  stop(false);
99 }
100 
102  if (!m_helper) {
103  return false;
104  }
105 
106  m_asprsModel.load();
107  updateInputOutput();
108  loadSettings();
109 
110  resetUI();
111  m_app->freezeUI(true);
112 
114  &ccCloudLayersDlg::mouseMoved);
115 
116  return ccOverlayDialog::start();
117 }
118 
119 void ccCloudLayersDlg::stop(bool accepted) {
120  if (m_mouseCircle && m_mouseCircle->isVisible()) pauseClicked();
121 
122  if (accepted && m_helper) {
123  m_helper->keepCurrentSFVisible();
124  }
125 
126  setPointCloud(nullptr);
127 
128  if (m_app) {
129  m_app->freezeUI(false);
130  }
131 
132  ccOverlayDialog::stop(accepted);
133 }
134 
136  if (m_helper) {
137  delete m_helper;
138  m_helper = nullptr;
139  }
140 
141  cbScalarField->clear();
142 
143  if (cloud) {
144  m_helper = new ccCloudLayersHelper(m_app, cloud);
145 
146  cbScalarField->addItems(m_helper->getScalarFields());
147  }
148 }
149 
150 void ccCloudLayersDlg::resetUI() {
151  pbStart->setEnabled(true);
152  pbPause->setEnabled(false);
153 }
154 
155 void ccCloudLayersDlg::initTableView() {
156  QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel(this);
157  proxyModel->setSourceModel(&m_asprsModel);
158  tableView->setModel(proxyModel);
159  tableView->setSortingEnabled(true);
160  tableView->sortByColumn(ccAsprsModel::CODE, Qt::AscendingOrder);
161 
162  // set column delegates
163  tableView->setItemDelegateForColumn(ccAsprsModel::COLOR,
164  new ccColorStyledDelegate(this));
165 
166  tableView->horizontalHeader()->setSectionResizeMode(ccAsprsModel::NAME,
167  QHeaderView::Stretch);
168  tableView->horizontalHeader()->setSectionResizeMode(
169  ccAsprsModel::VISIBLE, QHeaderView::ResizeToContents);
170  tableView->horizontalHeader()->setSectionResizeMode(
171  ccAsprsModel::CODE, QHeaderView::ResizeToContents);
172  tableView->horizontalHeader()->setSectionResizeMode(
173  ccAsprsModel::COLOR, QHeaderView::ResizeToContents);
174  tableView->horizontalHeader()->setSectionResizeMode(
175  ccAsprsModel::COUNT, QHeaderView::ResizeToContents);
176 }
177 
178 void ccCloudLayersDlg::saveSettings() {
179  QSettings settings;
180  settings.beginGroup("qCloudLayers");
181  {
182  if (cbScalarField->currentIndex() >= 0)
183  settings.setValue("ScalarField", cbScalarField->currentText());
184  if (cbInput->currentIndex() >= 0)
185  settings.setValue("InputClass", cbInput->currentText());
186  if (cbOutput->currentIndex() >= 0)
187  settings.setValue("OutputClass", cbOutput->currentText());
188 
189  settings.beginGroup("Window");
190  { settings.setValue("geometry", saveGeometry()); }
191  settings.sync();
192  }
193  settings.endGroup();
194 }
195 
196 void ccCloudLayersDlg::loadSettings() {
197  if (!m_helper) {
198  return;
199  }
200 
201  QSettings settings;
202  settings.beginGroup("qCloudLayers");
203  {
204  QString sfName = settings.value("ScalarField").toString();
205 
206  int sfIndex = m_helper->getScalarFields().indexOf(sfName);
207  if (sfIndex < 0) {
208  // previous scalar field not found
209  sfName = "Classification";
210  sfIndex = m_helper->getScalarFields().indexOf(sfName);
211  if (sfIndex < 0) {
212  // we'll take the first one
213  sfIndex = 0;
214  }
215  }
216 
217  cbScalarField->setCurrentIndex(sfIndex);
218 
219  QString inputName = settings.value("InputClass").toString();
220  const ccAsprsModel::AsprsItem* item = m_asprsModel.find(inputName);
221 
222  if (!item) {
223  int index = m_presets.indexOf(inputName);
224  cbInput->setCurrentIndex(std::max(0, index));
225  } else {
226  cbInput->setCurrentIndex(m_asprsModel.indexOf(inputName) +
227  m_presets.size());
228  }
229 
230  QString outputName = settings.value("OutputClass").toString();
231  int outIndex = m_asprsModel.indexOf(outputName);
232  cbOutput->setCurrentIndex(outIndex == -1 ? 0 : outIndex);
233 
234  settings.beginGroup("Window");
235  { restoreGeometry(settings.value("geometry").toByteArray()); }
236  settings.endGroup();
237  }
238  settings.endGroup();
239 }
240 
241 void ccCloudLayersDlg::addClicked() {
242  QModelIndex index = m_asprsModel.createNewItem();
243  tableView->selectRow(index.row());
244  tableView->setCurrentIndex(index);
245 
246  updateInputOutput();
247 }
248 
249 void ccCloudLayersDlg::deleteClicked() {
250  QItemSelectionModel* select = tableView->selectionModel();
251  if (!select->hasSelection()) return;
252 
253  if (QMessageBox::question(m_associatedWin, "Cloud layers plugin",
254  "Are you sure you want to delete this record(s)?",
255  QMessageBox::Yes,
256  QMessageBox::No) == QMessageBox::No) {
257  return;
258  }
259 
260  QModelIndexList mapIndices = select->selectedIndexes();
261  std::sort(mapIndices.begin(), mapIndices.end());
262 
263  QModelIndexList sourceIndices;
264  for (QModelIndex index : mapIndices) {
265  QModelIndex sourceIndex =
266  static_cast<QSortFilterProxyModel*>(tableView->model())
267  ->mapToSource(index);
268  sourceIndices.append(sourceIndex);
269  }
270 
271  ccAsprsModel::AsprsItem* to = m_asprsModel.getData().size() > 0
272  ? &(m_asprsModel.getData().front())
273  : nullptr;
274  for (int i = mapIndices.size(); i > 0; --i) {
276  m_asprsModel.getData()[sourceIndices[i - 1].row()];
277  int affected = m_helper ? m_helper->moveItem(from, to) : 0;
278  if (to) {
279  to->count += affected;
280  }
281 
282  tableView->model()->removeRows(mapIndices[i - 1].row(), 1);
283  }
284 
285  updateInputOutput();
286 
288 }
289 
290 void ccCloudLayersDlg::startClicked() {
291  if (nullptr == m_app->getActiveWindow()) {
292  return;
293  }
294 
295  ecvDisplayTools::SetPickingMode(ecvDisplayTools::PICKING_MODE::NO_PICKING);
296 
297  // set orthographic view (as this tool doesn't work in perspective mode)
301  m_mouseCircle->setVisible(true);
302 
303  pbStart->setEnabled(false);
304  pbPause->setEnabled(true);
305 }
306 
307 void ccCloudLayersDlg::pauseClicked() {
308  if (nullptr == m_app->getActiveWindow()) {
309  return;
310  }
311 
312  m_mouseCircle->setVisible(false);
314  ecvDisplayTools::PICKING_MODE::DEFAULT_PICKING);
316  ecvDisplayTools::RedrawDisplay(true, false);
317 
318  pbStart->setEnabled(true);
319  pbPause->setEnabled(false);
320 }
321 
322 void ccCloudLayersDlg::applyClicked() {
323  m_asprsModel.save();
324 
325  saveSettings();
326 
327  if (m_helper) {
328  m_helper->setVisible(true);
329  }
330 
331  stop(true);
332 }
333 
334 void ccCloudLayersDlg::closeClicked() {
335  if (m_helper) {
336  if (m_helper->hasChanges()) {
337  if (QMessageBox::question(
338  m_associatedWin, "Cloud layers plugin",
339  "The cloud has been modified, are you sure you want "
340  "exit?",
341  QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) {
342  return;
343  }
344  }
345 
346  m_helper->restoreState();
347  }
348 
349  stop(true);
350 }
351 
352 void ccCloudLayersDlg::mouseMoved(int x, int y, Qt::MouseButtons buttons) {
353  if (!m_helper) {
354  return;
355  }
356  if (buttons != Qt::LeftButton) {
357  return;
358  }
359 
360  ccGLCameraParameters camera;
362 
363  m_helper->projectCloud(camera);
364 
365  QPointF pos2D = ecvDisplayTools::ToCenteredGLCoordinates(x, y);
366  CCVector2 center(static_cast<PointCoordinateType>(pos2D.x()),
367  static_cast<PointCoordinateType>(pos2D.y()));
368 
369  int radius = m_mouseCircle->getRadiusPx();
370  std::map<ScalarType, int> affected;
371  m_helper->mouseMove(center, radius * radius, affected);
372 
373  // update point counts
374  for (const auto& kv : affected) {
375  auto item = m_asprsModel.find(kv.first);
376  if (item) item->count += kv.second;
377  }
378 
379  m_asprsModel.refreshData();
380 }
381 
382 bool ccCloudLayersDlg::eventFilter(QObject* obj, QEvent* event) {
383  if (event->type() == QEvent::KeyPress) {
384  QKeyEvent* ev = static_cast<QKeyEvent*>(event);
385  if (ev->key() == Qt::Key::Key_Alt) {
386  swapInputOutput();
387  m_mouseCircle->setAllowScroll(false);
388  }
389  } else if (event->type() == QEvent::KeyRelease) {
390  QKeyEvent* ev = static_cast<QKeyEvent*>(event);
391  if (ev->key() == Qt::Key::Key_Alt) {
392  swapInputOutput();
393  m_mouseCircle->setAllowScroll(true);
394  }
395  }
396 
397  return false;
398 }
399 
400 void ccCloudLayersDlg::scalarFieldIndexChanged(int index) {
401  if (m_helper) {
402  m_helper->setScalarFieldIndex(index);
403  m_helper->apply(m_asprsModel.getData());
404  m_helper->saveState();
405  }
406 
407  // refresh point count
408  m_asprsModel.refreshData();
409 }
410 
411 void ccCloudLayersDlg::inputClassIndexChanged(int index) {
412  if (!m_helper) {
413  return;
414  }
416  if (cbInput->currentIndex() < 0) {
417  params.anyPoints = false;
418  params.visiblePoints = false;
419  params.input = nullptr;
420 
421  return;
422  }
423 
424  QString inputName = cbInput->itemText(cbInput->currentIndex());
425  params.anyPoints = (inputName == m_presets[0]);
426  params.visiblePoints = (inputName == m_presets[1]);
427  params.input = nullptr;
428 
429  if (!(params.anyPoints || params.visiblePoints))
430  params.input = m_asprsModel.find(inputName);
431 }
432 
433 void ccCloudLayersDlg::outputClassIndexChanged(int index) {
434  if (!m_helper) {
435  return;
436  }
438  if (cbInput->currentIndex() < 0) {
439  params.output = nullptr;
440  return;
441  }
442 
443  QString outputName = cbOutput->itemText(cbOutput->currentIndex());
444  params.output = m_asprsModel.find(outputName);
445 }
446 
447 void ccCloudLayersDlg::codeChanged(ccAsprsModel::AsprsItem& item, int oldCode) {
448  if (m_helper) {
449  m_helper->changeCode(item, static_cast<ScalarType>(oldCode));
450  }
451 }
452 
453 void ccCloudLayersDlg::colorChanged(ccAsprsModel::AsprsItem& item) {
454  if (!m_helper) {
455  return;
456  }
457 
458  item.count = m_helper->apply(item, true);
459 
460  // refresh point count
461  m_asprsModel.refreshData();
462 }
463 
464 void ccCloudLayersDlg::tableViewDoubleClicked(const QModelIndex& index) {
465  if (index.column() != ccAsprsModel::COLOR) return;
466 
467  QColor currColor =
468  index.model()->data(index, Qt::DisplayRole).value<QColor>();
469  QColor color = QColorDialog::getColor(currColor, this, "Pick a color",
470  QColorDialog::DontUseNativeDialog);
471 
472  if (color.isValid() && color != currColor)
473  tableView->model()->setData(index, color, Qt::EditRole);
474 }
475 
476 void ccCloudLayersDlg::updateInputOutput() {
477  auto data = m_asprsModel.getData();
478 
479  cbInput->clear();
480  cbInput->addItems(m_presets);
481  for (int i = 0; i < data.size(); ++i) cbInput->addItem(data[i].name);
482 
483  cbOutput->clear();
484  for (int i = 0; i < data.size(); ++i) cbOutput->addItem(data[i].name);
485 }
486 
487 void ccCloudLayersDlg::swapInputOutput() {
488  int inputIndex = cbInput->currentIndex();
489  int outputIndex = cbOutput->currentIndex();
490 
491  if (inputIndex < 0 || outputIndex < 0) return;
492 
493  QString text = cbInput->itemText(cbInput->currentIndex());
494  bool isPresets = m_presets.contains(text);
495 
496  if (!isPresets) {
497  cbInput->setCurrentIndex(outputIndex + m_presets.size());
498  cbOutput->setCurrentIndex(inputIndex - m_presets.size());
499  }
500 
501  inputIndex = cbInput->currentIndex();
502  outputIndex = cbOutput->currentIndex();
503 }
MouseEvent event
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
math::float4 color
cmdLineReadable * params[]
void colorChanged(AsprsItem &item)
void refreshData()
AsprsItem * find(QString name)
int indexOf(QString name) const
void save() const
QModelIndex createNewItem()
void codeChanged(AsprsItem &item, int oldCode)
const QList< AsprsItem > & getData() const
Definition: ccAsprsModel.h:69
void stop(bool accepted) override
Stops process/dialog.
void setPointCloud(ccPointCloud *cloud)
bool start() override
inherited from ccOverlayDialog
virtual ~ccCloudLayersDlg()
Destructor.
ccCloudLayersDlg(ecvMainAppInterface *app, QWidget *parent=nullptr)
Default constructor.
void projectCloud(const ccGLCameraParameters &camera)
void apply(QList< ccAsprsModel::AsprsItem > &items)
int moveItem(const ccAsprsModel::AsprsItem &from, const ccAsprsModel::AsprsItem *to, bool redrawDisplay=false)
void mouseMove(const CCVector2 &center, float squareDist, std::map< ScalarType, int > &affected)
void changeCode(const ccAsprsModel::AsprsItem &item, ScalarType oldCode)
void setScalarFieldIndex(int index)
void setVisible(bool value)
virtual bool isVisible() const
Returns whether entity is visible or not.
virtual void setVisible(bool state)
Sets entity visibility.
int getRadiusPx() const
Definition: ccMouseCircle.h:35
void setAllowScroll(bool state)
Definition: ccMouseCircle.h:44
Generic overlay dialog interface.
virtual void stop(bool accepted)
Stops process/dialog.
virtual bool start()
Starts process.
QWidget * m_associatedWin
Associated (MDI) window.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
static void GetGLCameraParameters(ccGLCameraParameters &params)
Returns the current OpenGL camera parameters.
static QPointF ToCenteredGLCoordinates(int x, int y)
void mouseMoved(int x, int y, Qt::MouseButtons buttons)
Signal emitted when the mouse is moved.
static ecvDisplayTools * TheInstance()
static void SetInteractionMode(INTERACTION_FLAGS flags)
static void SetPerspectiveState(bool state, bool objectCenteredView)
Set perspective state/mode.
static void SetPickingMode(PICKING_MODE mode=DEFAULT_PICKING)
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
Main application interface (for plugins)
virtual QWidget * getActiveWindow()=0
virtual void freezeUI(bool state)=0
Freezes/unfreezes UI.
int max(int a, int b)
Definition: cutil_math.h:48
OpenGL camera parameters.