ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
AsciiOpenDlg.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 <ui_openAsciiFileDlg.h>
9 
10 // Local
11 #include "AsciiOpenDlg.h"
12 #include "FileIOFilter.h"
13 
14 // CV_DB_LIB
15 #include <ecvPointCloud.h>
16 
17 // Qt
18 #include <QComboBox>
19 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
20 #include <QDesktopWidget>
21 #endif
22 #include <QDialogButtonBox>
23 #include <QFile>
24 #include <QLineEdit>
25 #include <QMessageBox>
26 #include <QPushButton>
27 #include <QScreen>
28 #include <QSpinBox>
29 #include <QTableWidget>
30 #include <QTableWidgetItem>
31 #include <QTextStream>
32 #include <QToolButton>
33 
34 // Qt5/Qt6 Compatibility
35 #include <QtCompat.h>
36 
37 // system
38 #include <cassert>
39 #include <cstdio>
40 #include <cstring>
41 
42 // Semi-persistent value for max. cloud size
45 
46 // Max number of points/lines to detect a column as 'labels'
47 static unsigned s_maxLabelCount = 256;
48 
53  : separator(' '),
56  skipLines(0),
57  applyAll(false),
58  commaDecimal(false) {}
59 
61  void save(Ui_AsciiOpenDialog* ui) {
63  ui->extractSFNamesFrom1stLineCheckBox->isChecked();
64  maxPointCountPerCloud = ui->maxCloudSizeDoubleSpinBox->value();
65  separator = ui->lineEditSeparator->text().at(0);
66  skipLines = ui->spinBoxSkipLines->value();
67  commaDecimal = ui->commaDecimalCheckBox->isChecked();
68  }
69 
71  void load(Ui_AsciiOpenDialog* ui) const {
72  ui->extractSFNamesFrom1stLineCheckBox->setChecked(
74  ui->maxCloudSizeDoubleSpinBox->setValue(maxPointCountPerCloud);
75  ui->lineEditSeparator->blockSignals(true);
76  ui->lineEditSeparator->setText(separator);
77  ui->lineEditSeparator->blockSignals(false);
78  ui->spinBoxSkipLines->blockSignals(true);
79  ui->spinBoxSkipLines->setValue(skipLines);
80  ui->spinBoxSkipLines->blockSignals(false);
81  ui->commaDecimalCheckBox->blockSignals(true);
82  ui->commaDecimalCheckBox->setChecked(commaDecimal);
83  ui->commaDecimalCheckBox->setEnabled(separator != ',');
84  ui->commaDecimalCheckBox->blockSignals(false);
85  }
86 
88  QChar separator;
91  int skipLines;
92  bool applyAll;
94 };
95 
98 
100  : QDialog(parent),
101  m_ui(new Ui_AsciiOpenDialog),
102  m_skippedLines(0),
103  m_separator(' '),
104  m_averageLineSize(-1.0),
105  m_columnsCount(0) {
106  m_ui->setupUi(this);
107 
108  // spinBoxSkipLines->setValue(0);
109  m_ui->commentLinesSkippedLabel->hide();
110 
111  connect(m_ui->applyButton, &QPushButton::clicked, this,
113  connect(m_ui->applyAllButton, &QPushButton::clicked, this,
115  connect(m_ui->cancelButton, &QPushButton::clicked, this,
116  &AsciiOpenDlg::reject);
117  connect(m_ui->lineEditSeparator, &QLineEdit::textChanged, this,
119  connect(m_ui->commaDecimalCheckBox, &QCheckBox::toggled, this,
121  connect(m_ui->spinBoxSkipLines,
122  static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
124 
125  // shortcut buttons
126  connect(m_ui->toolButtonShortcutSpace, &QToolButton::clicked, this,
128  connect(m_ui->toolButtonShortcutComma, &QToolButton::clicked, this,
130  connect(m_ui->toolButtonShortcutSemicolon, &QToolButton::clicked, this,
132 
133  m_ui->maxCloudSizeDoubleSpinBox->setMaximum(
135  m_ui->maxCloudSizeDoubleSpinBox->setValue(s_maxCloudSizeDoubleSpinBoxValue);
136 
137  QSize screenSize = QGuiApplication::primaryScreen()->geometry().size();
138 
139  setMaximumSize(screenSize);
140 }
141 
143  delete m_ui;
144  m_ui = nullptr;
145 }
146 
147 void AsciiOpenDlg::setInput(const QString& filename,
148  QTextStream* stream /*=nullptr*/) {
150  m_stream = stream;
151 
152  // update title
153  m_ui->lineEditFileName->setText(m_filename);
154 
155  updateTable();
156 
158 }
159 
161  const QList<QChar> separators{QChar(' '), QChar(','), QChar(';')};
162 
163  // We try all default separators...
164  size_t maxValidColumnCount = 0;
165  QChar bestSep = separators.front();
166  for (QChar sep : separators) {
167  setSeparator(sep); // this eventually calls 'updateTable'
168 
169  //...until we find one that gives us at least 3 valid colums
170  size_t validColumnCount = 0;
171  for (ColumnType type : m_columnType) {
172  if (type != TEXT) {
173  ++validColumnCount;
174  }
175  }
176 
177  if (validColumnCount > 2) {
178  return;
179  } else if (validColumnCount > maxValidColumnCount) {
180  maxValidColumnCount = validColumnCount;
181  bestSep = sep;
182  }
183  }
184 
185  // if we are here, it means that we couldn't find a configuration
186  // with 3 valid columns (we use the best guess in this case)
187  setSeparator(bestSep); // this eventually calls 'updateTable'
188 }
189 
190 void AsciiOpenDlg::setSkippedLines(int linesCount) {
191  if (linesCount < 0) return;
192 
193  m_skippedLines = static_cast<unsigned>(linesCount);
194 
195  updateTable();
196 }
197 
198 static bool CouldBeX(const QString& colHeader) {
199  return colHeader.startsWith(AsciiHeaderColumns::X().toUpper());
200 }
201 static bool CouldBeY(const QString& colHeader) {
202  return colHeader.startsWith(AsciiHeaderColumns::Y().toUpper());
203 }
204 static bool CouldBeZ(const QString& colHeader) {
205  return colHeader.startsWith(AsciiHeaderColumns::Z().toUpper());
206 }
207 static bool CouldBeRf(const QString& colHeader) {
208  return colHeader == AsciiHeaderColumns::Rf().toUpper();
209 }
210 static bool CouldBeGf(const QString& colHeader) {
211  return colHeader == AsciiHeaderColumns::Gf().toUpper();
212 }
213 static bool CouldBeBf(const QString& colHeader) {
214  return colHeader == AsciiHeaderColumns::Bf().toUpper();
215 }
216 static bool CouldBeAf(const QString& colHeader) {
217  return colHeader == AsciiHeaderColumns::Af().toUpper();
218 }
219 static bool CouldBeR(const QString& colHeader) {
220  return colHeader == AsciiHeaderColumns::R().toUpper() ||
221  colHeader.contains("RED");
222 }
223 static bool CouldBeG(const QString& colHeader) {
224  return colHeader == AsciiHeaderColumns::G().toUpper() ||
225  colHeader.contains("GREEN");
226 }
227 static bool CouldBeB(const QString& colHeader) {
228  return colHeader == AsciiHeaderColumns::B().toUpper() ||
229  colHeader.contains("BLUE");
230 }
231 static bool CouldBeA(const QString& colHeader) {
232  return colHeader == AsciiHeaderColumns::A().toUpper() ||
233  colHeader.contains("ALPHA");
234 }
235 static bool CouldBeNx(const QString& colHeader) {
236  return colHeader.startsWith(AsciiHeaderColumns::Nx().toUpper()) ||
237  (colHeader.contains("NORM") && colHeader.contains("X"));
238 }
239 static bool CouldBeNy(const QString& colHeader) {
240  return colHeader.startsWith(AsciiHeaderColumns::Ny().toUpper()) ||
241  (colHeader.contains("NORM") && colHeader.contains("Y"));
242 }
243 static bool CouldBeNz(const QString& colHeader) {
244  return colHeader.startsWith(AsciiHeaderColumns::Nz().toUpper()) ||
245  (colHeader.contains("NORM") && colHeader.contains("Z"));
246 }
247 
248 static bool CouldBeGrey(const QString& colHeader) {
249  return colHeader == AsciiHeaderColumns::Grey().toUpper();
250 }
251 static bool CouldBeRGBi(const QString& colHeader) {
252  return colHeader == AsciiHeaderColumns::RGB32i().toUpper();
253 }
254 static bool CouldBeRGBf(const QString& colHeader) {
255  return colHeader == AsciiHeaderColumns::RGB32f().toUpper();
256 }
257 static bool CouldBeScal(const QString& colHeader) {
258  return colHeader.contains("SCALAR");
259 }
260 static bool CouldBeLabel(const QString& colHeader) {
261  return colHeader.contains("LABEL") || colHeader.contains("NAME");
262 }
263 
264 static const unsigned MAX_COLUMNS =
265  512; // maximum number of columns that can be handled
266 static const unsigned LINES_READ_FOR_STATS =
267  200; // number of lines read for stats
268 static const unsigned DISPLAYED_LINES = 20; // number of displayed lines
269 
270 static unsigned X_BIT = 1;
271 static unsigned Y_BIT = 2;
272 static unsigned Z_BIT = 4;
273 static unsigned W_BIT = 8;
274 static unsigned XYZ_BITS = X_BIT | Y_BIT | Z_BIT;
275 static unsigned XYZW_BITS = XYZ_BITS | W_BIT;
276 
277 static int EnabledBits(unsigned bitField) {
278  int count = 0;
279  if (bitField & X_BIT) ++count;
280  if (bitField & Y_BIT) ++count;
281  if (bitField & Z_BIT) ++count;
282  if (bitField & W_BIT) ++count;
283 
284  return count;
285 }
286 
287 void AsciiOpenDlg::onSeparatorChange(const QString& separator) {
288  assert(separator.size() == 1);
289  if (separator.length() < 1) {
290  m_ui->asciiCodeLabel->setText("Enter a valid character!");
291  m_ui->buttonWidget->setEnabled(false);
292  m_ui->tableWidget->clear();
293  m_columnType.clear();
294  return;
295  }
296 
297  // new separator
298  m_separator = separator[0];
299  m_ui->asciiCodeLabel->setText(
300  QString("(ASCII code: %1)").arg(m_separator.unicode()));
301 
302  m_headerLine.clear(); // to force re-assignation of columns!
303  m_columnType.clear();
304 
305  updateTable();
306 }
307 
309  return m_ui->commaDecimalCheckBox->isEnabled() &&
310  m_ui->commaDecimalCheckBox->isChecked();
311 }
312 
314  onSeparatorChange(m_ui->lineEditSeparator->text());
315 }
316 
318  m_ui->tableWidget->setEnabled(false);
319  // m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(false); //we can't do
320  // that here (as we may have restored the state of this checkbox before
321  // calling updateTable)
322 
323  bool hadValidHeader = !m_headerLine.isEmpty();
324  m_headerLine.clear();
325 
326  if (m_filename.isEmpty() && m_stream == nullptr) {
327  m_ui->tableWidget->clear();
328  m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(false);
329  return;
330  }
331 
332  // open the file
333  QFile file;
334  if (nullptr == m_stream) {
335  file.setFileName(m_filename);
336  if (!file.open(QFile::ReadOnly)) {
337  m_ui->tableWidget->clear();
338  m_columnType.clear();
339  m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(false);
340  return;
341  }
342  m_stream = new QTextStream(&file);
343  }
344  assert(m_stream);
345  m_stream->seek(0);
346 
347  // we skip first lines (if needed)
348  {
349  for (unsigned i = 0; i < m_skippedLines;) {
350  QString currentLine = m_stream->readLine();
351  if (currentLine.isNull()) {
352  // end of file reached
353  break;
354  }
355  if (currentLine.isEmpty()) {
356  // empty lines are ignored
357  continue;
358  }
359  // we keep track of the first line
360  if (i == 0) {
361  m_headerLine = currentLine;
362  }
363  ++i;
364  }
365  }
366 
367  // if the old setup has less than 3 columns, we forget it
368  if (m_columnsCount < 3) {
369  m_ui->tableWidget->clear();
370  m_columnType.clear();
371  m_columnsCount = 0;
372  }
373  m_ui->tableWidget->setRowCount(DISPLAYED_LINES +
374  1); //+1 for first line shifting
375 
376  unsigned lineCount = 0; // number of lines read
377  unsigned totalChars = 0; // total read characters (for stats)
378  unsigned columnsCount = 0; // max columns count per line
379  unsigned commentLines = 0; // number of comments line skipped
380 
381  std::vector<bool>
382  valueIsNumber; // identifies columns with numbers only [mandatory]
383  std::vector<bool> valueIsBelowOne; // identifies columns with values
384  // between -1 and 1 only
385  std::vector<bool>
386  valueIsInteger; // identifies columns with integer values only
387  std::vector<bool> valueIsBelow255; // identifies columns with integer
388  // values between 0 and 255 only
389 
390  bool commaAsDecimal = useCommaAsDecimal();
391  QLocale locale(commaAsDecimal ? QLocale::French : QLocale::English);
392  QChar decimalPoint = commaAsDecimal ? ',' : '.';
393  while (lineCount < LINES_READ_FOR_STATS) {
394  QString currentLine = m_stream->readLine();
395  if (currentLine.isNull()) {
396  // end of file reached
397  break;
398  }
399  if (currentLine.isEmpty()) {
400  // empty lines are ignored
401  continue;
402  }
403 
404  // we recognize "//" as the beginning of a comment
405  if (!currentLine.startsWith(
406  "//") /* || !currentLine.startsWith("#")*/) {
407  QStringList parts = currentLine.simplified().split(
409 
410  if (lineCount < DISPLAYED_LINES) {
411  unsigned partsCount = std::min(
412  MAX_COLUMNS, static_cast<unsigned>(parts.size()));
413  bool columnCountHasIncreased = (partsCount > columnsCount);
414 
415  // do we need to add one or several new columns?
416  if (columnCountHasIncreased) {
417  // we also extend vectors
418  for (unsigned i = columnsCount; i < partsCount; ++i) {
419  valueIsNumber.push_back(true);
420  valueIsBelowOne.push_back(true);
421  valueIsBelow255.push_back(true);
422  valueIsInteger.push_back(true);
423  }
424 
425  if (m_ui->tableWidget->columnCount() <
426  static_cast<int>(partsCount)) {
427  // DGM: at this stage we must not reduce the table!
428  // The first line is sometimes smaller than the next
429  // ones and we want to keep the widgets/configuration
430  // for the other columns!
431  m_ui->tableWidget->setColumnCount(partsCount);
432  } else if (m_ui->tableWidget->columnCount() >
433  static_cast<int>(partsCount)) {
434  // remove the unnecessary cells!
435  for (int i = static_cast<int>(partsCount);
436  i < m_ui->tableWidget->columnCount(); ++i) {
437  m_ui->tableWidget->setItem(lineCount + 1, i,
438  nullptr);
439  }
440  }
441  columnsCount = partsCount;
442  }
443 
444  // we fill the current row with extracted parts
445  for (unsigned i = 0; i < partsCount; ++i) {
446  QTableWidgetItem* newItem = new QTableWidgetItem(parts[i]);
447 
448  // test values
449  bool isANumber = false;
450  double value = locale.toDouble(parts[i], &isANumber);
451  if (!isANumber) {
452  valueIsNumber[i] = false;
453  valueIsBelowOne[i] = false;
454  valueIsInteger[i] = false;
455  valueIsBelow255[i] = false;
456  newItem->setBackground(QBrush(QColor(255, 160, 160)));
457  } else {
458  if (columnCountHasIncreased ||
459  (lineCount == 1 && !valueIsNumber[i])) {
460  // the previous lines were probably header lines
461  // we can forget about their content otherwise it
462  // will prevent us from detecting the right pattern
463  valueIsNumber[i] = true;
464  valueIsBelowOne[i] = true;
465  valueIsInteger[i] = true;
466  valueIsBelow255[i] = true;
467  }
468  valueIsBelowOne[i] =
469  valueIsBelowOne[i] && (std::abs(value) <= 1.0);
470  valueIsInteger[i] = valueIsInteger[i] &&
471  !parts[i].contains(decimalPoint);
472  valueIsBelow255[i] = valueIsBelow255[i] &&
473  valueIsInteger[i] &&
474  (value >= 0.0 && value <= 255.0);
475  }
476 
477  m_ui->tableWidget->setItem(
478  lineCount + 1, i,
479  newItem); //+1 for first line shifting
480  }
481  }
482 
483  totalChars += currentLine.size() + 1; //+1 for return char at eol
484  ++lineCount;
485  } else {
486  if (m_skippedLines == 0 && commentLines == 0) {
487  // if the very first line is a comment, then we force the user
488  // to skip it! this way it will be considered as a header
489  m_ui->spinBoxSkipLines->setMinimum(1);
490  m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(false);
491  return;
492  }
493  ++commentLines;
494  }
495  }
496 
497  file.close();
498 
499  // now we can reduce the table (if necessary)
500  if (m_ui->tableWidget->columnCount() > static_cast<int>(columnsCount)) {
501  m_ui->tableWidget->setColumnCount(columnsCount);
502  }
503 
504  // process header line
505  if (!m_headerLine.isEmpty()) {
506  m_headerLine = m_headerLine.trimmed();
507  int n = 0;
508  while (n < m_headerLine.size() && m_headerLine.at(n) == '/') {
509  ++n;
510  }
511  if (n != 0) {
512  m_headerLine.remove(0, n);
513  }
514 
515  QString displayHeader = m_headerLine;
516  if (m_headerLine.length() > 256) {
517  displayHeader = m_headerLine.left(256) + "...";
518  }
519  m_ui->headerLabel->setText(QString("Header: ") + displayHeader);
520  m_ui->headerLabel->setVisible(true);
521  } else {
522  m_ui->headerLabel->setVisible(false);
523  }
524 
525  m_ui->commentLinesSkippedLabel->setVisible(commentLines != 0);
526  if (commentLines) {
527  m_ui->commentLinesSkippedLabel->setText(
528  QString("+ %1 comment line(s) skipped").arg(commentLines));
529  }
530 
531  if (lineCount == 0 || columnsCount == 0) {
532  m_averageLineSize = -1.0;
533  m_ui->tableWidget->clear();
534  m_columnType.clear();
535  return;
536  }
537 
538  // average line size
539  m_averageLineSize = static_cast<double>(totalChars) / lineCount;
540  unsigned approximateTotalLineCount =
541  static_cast<unsigned>(file.size() / m_averageLineSize);
542 
543  // we add a type selector for each column
544  QStringList propsText;
545  {
546  propsText.reserve(ASCII_OPEN_DLG_TYPES_COUNT);
547  for (unsigned i = 0; i < ASCII_OPEN_DLG_TYPES_COUNT; i++) {
548  propsText << QString(ASCII_OPEN_DLG_TYPES_NAMES[i]);
549  }
550  }
551 
552  // remove unnecessary columns
553  {
554  while (columnsCount < m_columnsCount)
555  m_ui->tableWidget->removeColumn(--m_columnsCount);
556  if (m_columnType.size() > columnsCount)
557  m_columnType.resize(columnsCount, UNKNOWN);
558  for (unsigned i = lineCount + 1; i <= DISPLAYED_LINES; ++i)
559  m_ui->tableWidget->removeRow(i);
560  }
561 
562  // setup table and widgets
563  {
564  // Icons
565  static const QIcon xIcon(
566  QString::fromUtf8(":/Resources/images/typeXCoordinate.png"));
567  static const QIcon yIcon(
568  QString::fromUtf8(":/Resources/images/typeYCoordinate.png"));
569  static const QIcon zIcon(
570  QString::fromUtf8(":/Resources/images/typeZCoordinate.png"));
571  static const QIcon NormIcon(
572  QString::fromUtf8(":/Resources/images/typeNormal.png"));
573  static const QIcon RGBIcon(
574  QString::fromUtf8(":/Resources/images/typeRgbCcolor.png"));
575  static const QIcon GreyIcon(
576  QString::fromUtf8(":/Resources/images/typeGrayColor.png"));
577  static const QIcon ScalarIcon(
578  QString::fromUtf8(":/Resources/images/typeSF.png"));
579  static const QIcon LabelIcon(
580  QString::fromUtf8(":/Resources/images/dbLabelSymbol.png"));
581 
582  int columnWidth =
583  (m_ui->tableWidget->width() * 9) / (columnsCount * 10);
584  columnWidth = std::max(columnWidth, 80);
585 
586  for (unsigned i = 0; i < columnsCount; i++) {
587  QComboBox* columnHeaderWidget = static_cast<QComboBox*>(
588  m_ui->tableWidget->cellWidget(0, i));
589  QComboBox* _columnHeader = columnHeaderWidget;
590  if (!columnHeaderWidget) {
591  columnHeaderWidget = new QComboBox();
592  columnHeaderWidget->addItems(propsText);
593  columnHeaderWidget->setMaxVisibleItems(
595  columnHeaderWidget->setCurrentIndex(0);
596  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_X, xIcon);
597  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Y, yIcon);
598  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Z, zIcon);
599  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_NX, NormIcon);
600  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_NY, NormIcon);
601  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_NZ, NormIcon);
602  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_R, RGBIcon);
603  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_G, RGBIcon);
604  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_B, RGBIcon);
605  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_A, RGBIcon);
606  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Rf, RGBIcon);
607  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Gf, RGBIcon);
608  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Bf, RGBIcon);
609  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Af, RGBIcon);
610  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Grey, GreyIcon);
611  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_RGB32i, RGBIcon);
612  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_RGB32f, RGBIcon);
613  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Label,
614  LabelIcon);
615  columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Scalar,
616  ScalarIcon);
617 
618  connect(columnHeaderWidget,
619  static_cast<void (QComboBox::*)(int)>(
620  &QComboBox::currentIndexChanged),
622  }
623 
624  while (m_columnType.size() <= static_cast<size_t>(i))
625  m_columnType.push_back(UNKNOWN);
626  assert(m_columnType.size() >= static_cast<size_t>(i));
627 
628  if (!_columnHeader)
629  m_ui->tableWidget->setCellWidget(0, i, columnHeaderWidget);
630  m_ui->tableWidget->setColumnWidth(i, columnWidth);
631 
632  // a non-numerical column can't be valid
633  if (!valueIsNumber[i]) {
634  m_columnType[i] = TEXT;
635  } else {
636  // we must do this to ensure we can get a correct result.
637  // Otherwise, we may fail in such situations:
638  //
639  // FILE:
640  // Line1 : $ gps file
641  // Line2 : $ id name x y z
642  // Line3 : 500
643  // Line4 : 0 0001.JPG 753811.417453 4307200.381522
644  // 1957.803955 Linex : ...... Line503 : 499 0500.JPG
645  // 753630.672714 4307195.433217 1957.803955
646  //
647  // Description:
648  // once we open the file, we will get a %m_columnType with 5
649  // values of "TEXT" then if we choose to skip the 3 first lines,
650  // we get a %valueIsNumber with 5 "true" but the %m_columnType
651  // is still with 5 values of "TEXT" which leads to the failure!
652  m_columnType[i] = UNKNOWN;
653  }
654  }
655  }
656 
657  // auto-detect columns 'roles'
658  {
659  // DGM: bit flags now
660  unsigned assignedXYZFlags = 0;
661  unsigned assignedNormFlags = 0;
662  unsigned assignedRGBFlags = 0;
663 
664  // split header (if any)
665  QStringList headerParts = m_headerLine.simplified().split(
667  bool validHeader =
668  (headerParts.size() >= static_cast<int>(columnsCount));
669  m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(
670  validHeader); // can we consider the first ignored line as a
671  // header?
672  if (!validHeader) {
673  // no need to keep it (+ it will serve as flag later)
674  m_headerLine.clear();
675  } else if (!hadValidHeader) {
676  m_ui->extractSFNamesFrom1stLineCheckBox->setChecked(true);
677  for (ColumnType& type : m_columnType) {
678  if (type != TEXT) type = UNKNOWN;
679  }
680  // std::fill(m_columnType.begin(), m_columnType.end(), UNKNOWN);
681  // //if the header has changed, we force update of all columns!
682  }
683 
684  // first with the help of the header (if any)
685  bool labelColumnAssigned = false;
686  if (validHeader) {
687  for (unsigned i = 0; i < columnsCount; i++) {
688  // we try to guess columns if we have a valid header for the
689  // first time!
690  if (m_columnType[i] == UNKNOWN || m_columnType[i] == IGNORED) {
691  QComboBox* columnHeaderWidget = static_cast<QComboBox*>(
692  m_ui->tableWidget->cellWidget(0, i));
693  assert(columnHeaderWidget);
694 
695  columnHeaderWidget->blockSignals(true);
696 
697  QString colHeader = headerParts[i].toUpper();
698 
699  if ((assignedXYZFlags & X_BIT) == 0 &&
700  CouldBeX(colHeader)) {
701  // X
702  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_X);
703  assignedXYZFlags |=
704  X_BIT; // update bit field accordingly
705  m_columnType[i] = VALID;
706  } else if ((assignedXYZFlags & Y_BIT) == 0 &&
707  CouldBeY(colHeader)) {
708  // Y
709  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Y);
710  assignedXYZFlags |=
711  Y_BIT; // update bit field accordingly
712  m_columnType[i] = VALID;
713  } else if ((assignedXYZFlags & Z_BIT) == 0 &&
714  CouldBeZ(colHeader)) {
715  // Z
716  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Z);
717  assignedXYZFlags |=
718  Z_BIT; // update bit field accordingly
719  m_columnType[i] = VALID;
720  } else if ((assignedRGBFlags & X_BIT) == 0 &&
721  CouldBeRf(colHeader)) {
722  // Red
723  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Rf);
724  assignedRGBFlags |=
725  X_BIT; // update bit field accordingly
726  m_columnType[i] = VALID;
727  } else if ((assignedRGBFlags & Y_BIT) == 0 &&
728  CouldBeGf(colHeader)) {
729  // Green
730  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Gf);
731  assignedRGBFlags |=
732  Y_BIT; // update bit field accordingly
733  m_columnType[i] = VALID;
734  } else if ((assignedRGBFlags & Z_BIT) == 0 &&
735  CouldBeBf(colHeader)) {
736  // Blue
737  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Bf);
738  assignedRGBFlags |=
739  Z_BIT; // update bit field accordingly
740  m_columnType[i] = VALID;
741  } else if ((assignedRGBFlags & X_BIT) == 0 &&
742  CouldBeR(colHeader)) {
743  // Red
744  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_R);
745  assignedRGBFlags |=
746  X_BIT; // update bit field accordingly
747  m_columnType[i] = VALID;
748  } else if ((assignedRGBFlags & Y_BIT) == 0 &&
749  CouldBeG(colHeader)) {
750  // Green
751  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_G);
752  assignedRGBFlags |=
753  Y_BIT; // update bit field accordingly
754  m_columnType[i] = VALID;
755  } else if ((assignedRGBFlags & Z_BIT) == 0 &&
756  CouldBeB(colHeader)) {
757  // Blue
758  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_B);
759  assignedRGBFlags |=
760  Z_BIT; // update bit field accordingly
761  m_columnType[i] = VALID;
762  } else if ((assignedRGBFlags & W_BIT) == 0 &&
763  CouldBeA(colHeader)) {
764  // Blue
765  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_A);
766  assignedRGBFlags |=
767  W_BIT; // update bit field accordingly
768  m_columnType[i] = VALID;
769  } else if ((assignedNormFlags & X_BIT) == 0 &&
770  CouldBeNx(colHeader)) {
771  // Nx
772  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NX);
773  assignedNormFlags |=
774  X_BIT; // update bit field accordingly
775  m_columnType[i] = VALID;
776  } else if ((assignedNormFlags & Y_BIT) == 0 &&
777  CouldBeNy(colHeader)) {
778  // Ny
779  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NY);
780  assignedNormFlags |=
781  Y_BIT; // update bit field accordingly
782  m_columnType[i] = VALID;
783  } else if ((assignedNormFlags & Z_BIT) == 0 &&
784  CouldBeNz(colHeader)) {
785  // Nz
786  columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NZ);
787  assignedNormFlags |=
788  Z_BIT; // update bit field accordingly
789  m_columnType[i] = VALID;
790  } else if (CouldBeGrey(colHeader)) {
791  // Intensity
792  columnHeaderWidget->setCurrentIndex(
794  m_columnType[i] = VALID;
795  } else if (CouldBeRGBi(colHeader)) {
796  // RGBi
797  columnHeaderWidget->setCurrentIndex(
799  m_columnType[i] = VALID;
800  } else if (CouldBeRGBf(colHeader)) {
801  // RGBf
802  columnHeaderWidget->setCurrentIndex(
804  m_columnType[i] = VALID;
805  } else if (CouldBeScal(colHeader)) {
806  // scalar field
807  columnHeaderWidget->setCurrentIndex(
809  m_columnType[i] = VALID;
810  } else if (!labelColumnAssigned &&
811  approximateTotalLineCount < s_maxLabelCount &&
812  CouldBeLabel(colHeader)) // no need to promote
813  // labels if there are
814  // too many of them ;)
815  {
816  // label
817  columnHeaderWidget->setCurrentIndex(
819  m_columnType[i] = VALID;
820  labelColumnAssigned = true;
821  }
822 
823  columnHeaderWidget->blockSignals(false);
824  }
825  }
826  } // if (validHeader)
827 
828  // now for the auto-detection whithout header
829  {
830  for (unsigned i = 0; i < columnsCount; i++) {
831  if (m_columnType[i] == TEXT) {
832  if (!labelColumnAssigned && columnsCount > 1 &&
833  approximateTotalLineCount < s_maxLabelCount) {
834  QComboBox* columnHeaderWidget = static_cast<QComboBox*>(
835  m_ui->tableWidget->cellWidget(0, i));
836  assert(columnHeaderWidget);
837  columnHeaderWidget->blockSignals(true);
838  columnHeaderWidget->setCurrentIndex(
840  columnHeaderWidget->blockSignals(false);
841 
842  labelColumnAssigned = true; // There Can Be Only One!
843  }
844  continue;
845  }
846 
847  // now we deal with numerical values only
848  assert(valueIsNumber[i]);
849 
850  // first time? let's try to assign each column a type
851  if (m_columnType[i] == UNKNOWN && columnsCount > 1) {
852  QComboBox* columnHeaderWidget = static_cast<QComboBox*>(
853  m_ui->tableWidget->cellWidget(0, i));
854  assert(columnHeaderWidget);
855  columnHeaderWidget->blockSignals(true);
856  columnHeaderWidget->setCurrentIndex(-1);
857 
858  // by default, we assume that the first columns are always
859  // X,Y and Z
860  if (assignedXYZFlags < XYZ_BITS) {
861  // in rare cases, the first column is an index
862  if (columnsCount > 3 && i == 0 &&
863  (EnabledBits(assignedXYZFlags) == 0) &&
864  valueIsInteger[i] && i + 1 < columnsCount &&
865  !valueIsInteger[i + 1]) {
866  // let's consider it as a scalar
867  columnHeaderWidget->setCurrentIndex(
869  } else {
870  if (!(assignedXYZFlags & X_BIT)) {
871  columnHeaderWidget->setCurrentIndex(
873  assignedXYZFlags |=
874  X_BIT; // update bit field accordingly
875  } else if (!(assignedXYZFlags & Y_BIT)) {
876  columnHeaderWidget->setCurrentIndex(
878  assignedXYZFlags |=
879  Y_BIT; // update bit field accordingly
880  } else if (!(assignedXYZFlags & Z_BIT)) {
881  columnHeaderWidget->setCurrentIndex(
883  assignedXYZFlags |=
884  Z_BIT; // update bit field accordingly
885  }
886  }
887  } else {
888  // looks like RGB?
889  if (valueIsBelow255[i] && assignedRGBFlags < XYZ_BITS &&
890  (i + 2 - EnabledBits(assignedRGBFlags)) <
891  columnsCount // make sure that we can put
892  // all values there!
893  &&
894  (EnabledBits(assignedRGBFlags) > 0 ||
895  (valueIsBelow255[i + 1] &&
896  valueIsBelow255[i + 2])) // make sure that next
897  // values are also ok!
898  ) {
899  if (!(assignedRGBFlags & X_BIT)) {
900  columnHeaderWidget->setCurrentIndex(
902  assignedRGBFlags |=
903  X_BIT; // update bit field accordingly
904  } else if (!(assignedRGBFlags & Y_BIT)) {
905  columnHeaderWidget->setCurrentIndex(
907  assignedRGBFlags |=
908  Y_BIT; // update bit field accordingly
909  } else if (!(assignedRGBFlags & Z_BIT)) {
910  columnHeaderWidget->setCurrentIndex(
912  assignedRGBFlags |=
913  Z_BIT; // update bit field accordingly
914  } else if (!(assignedRGBFlags & W_BIT)) {
915  columnHeaderWidget->setCurrentIndex(
917  assignedRGBFlags |=
918  W_BIT; // update bit field accordingly
919  }
920  }
921  // looks like a normal vector?
922  else if (valueIsBelowOne[i] &&
923  assignedNormFlags < XYZ_BITS &&
924  (i + 2 - EnabledBits(assignedNormFlags) <
925  columnsCount) // make sure that we can put
926  // all values there!
927  &&
928  (EnabledBits(assignedNormFlags) > 0 ||
929  (valueIsBelowOne[i + 1] &&
930  valueIsBelowOne[i + 2])) // make sure that
931  // next values are
932  // also ok!
933  ) // make sure that next values are also ok!
934  {
935  if (!(assignedNormFlags & X_BIT)) {
936  columnHeaderWidget->setCurrentIndex(
938  assignedNormFlags |=
939  X_BIT; // update bit field accordingly
940  } else if (!(assignedNormFlags & Y_BIT)) {
941  columnHeaderWidget->setCurrentIndex(
943  assignedNormFlags |=
944  Y_BIT; // update bit field accordingly
945  } else if (!(assignedNormFlags & Z_BIT)) {
946  columnHeaderWidget->setCurrentIndex(
948  assignedNormFlags |=
949  Z_BIT; // update bit field accordingly
950  }
951  } else {
952  // maybe it's a scalar?
953  columnHeaderWidget->setCurrentIndex(
955  }
956  }
957 
958  if (columnHeaderWidget->currentIndex() >= 0) {
959  m_columnType[i] = VALID;
960  } else {
961  // we won't look at this column again
962  m_columnType[i] = IGNORED;
963  }
964 
965  columnHeaderWidget->blockSignals(false);
966  } else {
967  // we won't look at this column again
968  m_columnType[i] = IGNORED;
969  }
970  }
971  }
972  }
973 
974  m_columnsCount = columnsCount;
975 
976  m_ui->tableWidget->setEnabled(true);
977  m_ui->buttonWidget->setEnabled(true);
978 
979  // check for invalid columns
980  checkSelectedColumnsValidity(); // will eventually enable of disable the
981  // "OK" button
982 
983  m_ui->tableWidget->resizeColumnsToContents();
984 }
985 
987  // check for invalid columns
988  bool m_selectedInvalidColumns = false;
989  {
990  assert(m_columnType.size() == static_cast<size_t>(m_columnsCount));
991  assert(m_ui->tableWidget->columnCount() >=
992  static_cast<int>(m_columnsCount));
993  m_ui->show2DLabelsCheckBox->setEnabled(false);
994  for (unsigned i = 0; i < m_columnsCount; i++) {
995  QComboBox* columnHeaderWidget = static_cast<QComboBox*>(
996  m_ui->tableWidget->cellWidget(0, i));
997 
998  if (columnHeaderWidget->currentIndex() == ASCII_OPEN_DLG_Label) {
999  m_ui->show2DLabelsCheckBox->setEnabled(true);
1000  } else if (m_columnType[i] == TEXT &&
1001  columnHeaderWidget->currentIndex() != 0) {
1002  // text columns shouldn't be selected (other than for Labels)
1003  m_selectedInvalidColumns |= true;
1004  }
1005  }
1006  }
1007 
1008  m_ui->applyAllButton->setEnabled(!m_selectedInvalidColumns);
1009  m_ui->applyButton->setEnabled(!m_selectedInvalidColumns);
1010 }
1011 
1013  QString& errorMessage) {
1014  // two requirements:
1015  //- at least 2 coordinates must be defined
1016  //- apart from SFs, only one column assignment per property
1017  std::vector<unsigned> counters(ASCII_OPEN_DLG_TYPES_COUNT, 0);
1018  {
1019  for (size_t i = 0; i < sequence.size(); i++)
1020  ++counters[sequence[i].type];
1021  }
1022 
1023  // check for doublons
1024  {
1025  for (size_t i = 1; i < ASCII_OPEN_DLG_Scalar; i++) {
1026  if (counters[i] > 1) {
1027  errorMessage = QString("'%1' defined at least twice!")
1028  .arg(ASCII_OPEN_DLG_TYPES_NAMES[i]);
1029  return false;
1030  }
1031  }
1032  }
1033 
1034  unsigned char coordIsDefined[3] = {counters[ASCII_OPEN_DLG_X] != 0,
1035  counters[ASCII_OPEN_DLG_Y] != 0,
1036  counters[ASCII_OPEN_DLG_Z] != 0};
1037 
1038  if (coordIsDefined[0] + coordIsDefined[1] + coordIsDefined[2] < 2) {
1039  errorMessage = "At least 2 vertex coordinates must be defined!";
1040  return false;
1041  }
1042 
1043  return true;
1044 }
1045 
1047  QString errorMessage;
1048  if (!CheckOpenSequence(getOpenSequence(), errorMessage)) {
1049  QMessageBox::warning(nullptr, "Error", errorMessage);
1050  return false;
1051  } else {
1053  m_ui->maxCloudSizeDoubleSpinBox->value();
1054  accept();
1055  return true;
1056  }
1057 }
1058 
1060  if (!apply()) return;
1061 
1062  // backup current open sequence
1066 }
1067 
1069 
1071  if (!s_asciiOpenContext.applyAll) return false;
1072 
1073  // restore previous dialog state
1076  m_skippedLines =
1077  static_cast<unsigned>(std::max(0, s_asciiOpenContext.skipLines));
1078  updateTable();
1079 
1080  // saved sequence and cloud content don't match!!!
1081  if (static_cast<size_t>(m_columnsCount) !=
1082  s_asciiOpenContext.sequence.size()) {
1083  s_asciiOpenContext.applyAll = false; // cancel the 'Apply All' effect
1084  return false;
1085  }
1086 
1087  // Restore columns attributes
1088  for (unsigned i = 0; i < m_columnsCount; i++) {
1089  QComboBox* combo =
1090  static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
1091  if (!combo) // yes, it happens if all lines are skipped!
1092  {
1094  false; // cancel the 'Apply All' effect
1095  return false;
1096  }
1098  combo->setCurrentIndex(item.type);
1099  }
1100 
1101  QString errorMessage;
1102  if (!CheckOpenSequence(s_asciiOpenContext.sequence, errorMessage)) {
1103  s_asciiOpenContext.applyAll = false; // cancel the 'Apply All' effect
1104  }
1106 }
1107 
1109  Sequence seq;
1110 
1111  if (m_columnsCount != 0) {
1112  // shall we extract headerParts?
1113  QStringList headerParts;
1114  if (!m_headerLine.isEmpty() &&
1115  m_ui->extractSFNamesFrom1stLineCheckBox->isEnabled() &&
1116  m_ui->extractSFNamesFrom1stLineCheckBox->isChecked()) {
1117  headerParts = m_headerLine.simplified().split(
1119  }
1120 
1121  seq.reserve(m_columnsCount - 1);
1122  for (unsigned i = 0; i < m_columnsCount; i++) {
1123  const QComboBox* combo = static_cast<QComboBox*>(
1124  m_ui->tableWidget->cellWidget(0, i));
1125  if (!combo) // yes, it happens if all lines are skipped!
1126  break;
1127  seq.emplace_back(
1128  static_cast<CC_ASCII_OPEN_DLG_TYPES>(combo->currentIndex()),
1129  headerParts.size() > static_cast<int>(i) ? headerParts[i]
1130  : QString());
1131  }
1132  }
1133 
1134  return seq;
1135 }
1136 
1138  // less than 6 columns: we probably have a 3D point (3 columns)
1139  // and 2 other columns (i.e. scalar fields) --> no ambiguity
1140  if (getColumnsCount() < 6) return true;
1141 
1142  // no header
1143  if (m_headerLine.isEmpty()) return false;
1144 
1146  QStringList headerParts = m_headerLine.simplified().split(
1148 
1149  // not enough column headers?
1150  if (headerParts.size() < static_cast<int>(seq.size())) return false;
1151 
1152  for (int i = 0; i < headerParts.size(); ++i) {
1153  // column header
1154  QString colHeader = headerParts[i].toUpper();
1155  // column type
1156  switch (seq[i].type) {
1157  case ASCII_OPEN_DLG_None:
1158  break;
1159  case ASCII_OPEN_DLG_X:
1160  if (!CouldBeX(colHeader)) return false;
1161  break;
1162  case ASCII_OPEN_DLG_Y:
1163  if (!CouldBeY(colHeader)) return false;
1164  break;
1165  case ASCII_OPEN_DLG_Z:
1166  if (!CouldBeZ(colHeader)) return false;
1167  break;
1168  case ASCII_OPEN_DLG_NX:
1169  if (!CouldBeNx(colHeader)) return false;
1170  break;
1171  case ASCII_OPEN_DLG_NY:
1172  if (!CouldBeNy(colHeader)) return false;
1173  break;
1174  case ASCII_OPEN_DLG_NZ:
1175  if (!CouldBeNz(colHeader)) return false;
1176  break;
1177  case ASCII_OPEN_DLG_R:
1178  case ASCII_OPEN_DLG_Rf:
1179  if (!CouldBeR(colHeader)) return false;
1180  break;
1181  case ASCII_OPEN_DLG_G:
1182  case ASCII_OPEN_DLG_Gf:
1183  if (!CouldBeG(colHeader)) return false;
1184  break;
1185  case ASCII_OPEN_DLG_B:
1186  case ASCII_OPEN_DLG_Bf:
1187  if (!CouldBeB(colHeader)) return false;
1188  break;
1189  case ASCII_OPEN_DLG_A:
1190  case ASCII_OPEN_DLG_Af:
1191  if (!CouldBeA(colHeader)) return false;
1192  break;
1193  case ASCII_OPEN_DLG_Grey:
1194  if (!CouldBeGrey(colHeader) && !colHeader.contains("INT"))
1195  return false;
1196  break;
1197  case ASCII_OPEN_DLG_Scalar:
1198  // a SF name can be anything!
1199  break;
1200  case ASCII_OPEN_DLG_RGB32i:
1201  if (!CouldBeRGBi(colHeader) && !colHeader.contains("RGB"))
1202  return false;
1203  break;
1204  case ASCII_OPEN_DLG_RGB32f:
1205  if (!CouldBeRGBf(colHeader) && !colHeader.contains("RGB"))
1206  return false;
1207  break;
1208  case ASCII_OPEN_DLG_Label:
1209  if (!CouldBeLabel(colHeader)) return false;
1210  break;
1211  default:
1212  // unhandled case?!
1213  assert(false);
1214  return false;
1215  }
1216  }
1217 
1218  return true;
1219 }
1220 
1222  if (!m_columnsCount) {
1223  return;
1224  }
1225 
1226  // we get the signal sender
1227  QObject* obj = sender();
1228  if (!obj) {
1229  assert(false);
1230  return;
1231  }
1232 
1233  // it should be a QComboBox
1234  QComboBox* changedCombo = qobject_cast<QComboBox*>(obj);
1235  if (!changedCombo) {
1236  assert(false);
1237  return;
1238  }
1239 
1240  // now we look which column's combobox it is
1241  for (unsigned i = 0; i < m_columnsCount; i++) {
1242  QComboBox* combo =
1243  static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
1244  // we found the right element
1245  if (changedCombo == combo) {
1246  if (index == int(ASCII_OPEN_DLG_X) ||
1247  index == int(ASCII_OPEN_DLG_NX) ||
1248  index == int(ASCII_OPEN_DLG_R) ||
1249  index == int(ASCII_OPEN_DLG_Rf)) {
1250  // Auto select the next columns type
1251  if (i + 2 < m_columnsCount) {
1252  QComboBox* nextCombo = static_cast<QComboBox*>(
1253  m_ui->tableWidget->cellWidget(0, i + 1));
1254  QComboBox* nextNextCombo = static_cast<QComboBox*>(
1255  m_ui->tableWidget->cellWidget(0, i + 2));
1256  // if the two next columns have no assigned type, we set
1257  // them auto.
1258  if (nextCombo->currentIndex() == int(ASCII_OPEN_DLG_None) &&
1259  nextNextCombo->currentIndex() ==
1260  int(ASCII_OPEN_DLG_None)) {
1261  nextCombo->blockSignals(true);
1262  nextNextCombo->blockSignals(true);
1263 
1264  if (index == int(ASCII_OPEN_DLG_X)) {
1265  nextCombo->setCurrentIndex(ASCII_OPEN_DLG_Y);
1266  nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_Z);
1267  } else if (index == int(ASCII_OPEN_DLG_NX)) {
1268  nextCombo->setCurrentIndex(ASCII_OPEN_DLG_NY);
1269  nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_NZ);
1270  } else if (index == int(ASCII_OPEN_DLG_R)) {
1271  nextCombo->setCurrentIndex(ASCII_OPEN_DLG_G);
1272  nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_B);
1273  } else if (index == int(ASCII_OPEN_DLG_Rf)) {
1274  nextCombo->setCurrentIndex(ASCII_OPEN_DLG_Gf);
1275  nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_Bf);
1276  }
1277  }
1278 
1279  nextCombo->blockSignals(false);
1280  nextNextCombo->blockSignals(false);
1281  }
1282  }
1283  } else if (index <
1284  ASCII_OPEN_DLG_Scalar) // check that the other combo as the
1285  // same index (apart from SF)
1286  {
1287  if (combo->currentIndex() == index) {
1288  combo->blockSignals(true);
1289  combo->setCurrentIndex(ASCII_OPEN_DLG_None);
1290  combo->blockSignals(false);
1291  }
1292  }
1293  }
1294 
1295  checkSelectedColumnsValidity(); // will eventually enable of disable the
1296  // "OK" button
1297 }
1298 
1300  if (!m_columnsCount) return;
1301 
1302  // we get the signal sender
1303  QObject* obj = sender();
1304  if (!obj) return;
1305 
1306  // it should be a QToolButton (could we test this?)
1307  QToolButton* shortcutButton = static_cast<QToolButton*>(obj);
1308 
1309  char newSeparator = 0;
1310  if (shortcutButton == m_ui->toolButtonShortcutSpace)
1311  newSeparator = 32;
1312  else if (shortcutButton == m_ui->toolButtonShortcutComma)
1313  newSeparator = 44;
1314  else if (shortcutButton == m_ui->toolButtonShortcutSemicolon)
1315  newSeparator = 59;
1316 
1317  if (newSeparator != 0 && getSeparator() != newSeparator) {
1318  setSeparator(QChar(newSeparator));
1319  }
1320 }
1321 
1323  m_ui->commaDecimalCheckBox->blockSignals(true);
1324  if (sep == QChar(',')) // comma
1325  {
1326  m_ui->commaDecimalCheckBox->setEnabled(false);
1327  // m_ui->commaDecimalCheckBox->setChecked(false);
1328  } else {
1329  m_ui->commaDecimalCheckBox->setEnabled(true);
1330  }
1331  m_ui->commaDecimalCheckBox->blockSignals(false);
1332  m_ui->lineEditSeparator->setText(sep);
1333 }
1334 
1336  return static_cast<unsigned>(
1337  floor(m_ui->maxCloudSizeDoubleSpinBox->value() * 1.0e6));
1338 }
1339 
1341  return m_ui->show2DLabelsCheckBox->isEnabled() &&
1342  m_ui->show2DLabelsCheckBox->isChecked();
1343 }
static bool CouldBeR(const QString &colHeader)
static unsigned W_BIT
static bool CouldBeGf(const QString &colHeader)
static bool CouldBeRf(const QString &colHeader)
static bool CouldBeA(const QString &colHeader)
static bool CouldBeNy(const QString &colHeader)
static bool CouldBeY(const QString &colHeader)
static bool CouldBeNx(const QString &colHeader)
static unsigned XYZ_BITS
static unsigned XYZW_BITS
static double s_maxCloudSizeDoubleSpinBoxValue
static AsciiOpenContext s_asciiOpenContext
Semi-persistent loading context.
static bool CouldBeZ(const QString &colHeader)
static bool CouldBeG(const QString &colHeader)
static const unsigned MAX_COLUMNS
static bool CouldBeAf(const QString &colHeader)
static bool CouldBeB(const QString &colHeader)
static bool CouldBeRGBi(const QString &colHeader)
static bool CouldBeNz(const QString &colHeader)
static bool CouldBeBf(const QString &colHeader)
static int EnabledBits(unsigned bitField)
static bool CouldBeScal(const QString &colHeader)
static bool CouldBeRGBf(const QString &colHeader)
static const unsigned LINES_READ_FOR_STATS
static bool CouldBeLabel(const QString &colHeader)
static unsigned Z_BIT
static unsigned X_BIT
static unsigned s_maxLabelCount
static const unsigned DISPLAYED_LINES
static bool CouldBeGrey(const QString &colHeader)
static bool CouldBeX(const QString &colHeader)
static unsigned Y_BIT
const unsigned ASCII_OPEN_DLG_TYPES_COUNT
Definition: AsciiOpenDlg.h:20
const char ASCII_OPEN_DLG_TYPES_NAMES[ASCII_OPEN_DLG_TYPES_COUNT][20]
Definition: AsciiOpenDlg.h:72
CC_ASCII_OPEN_DLG_TYPES
Definition: AsciiOpenDlg.h:22
@ ASCII_OPEN_DLG_Label
Definition: AsciiOpenDlg.h:41
@ ASCII_OPEN_DLG_Grey
Definition: AsciiOpenDlg.h:38
@ ASCII_OPEN_DLG_NX
Definition: AsciiOpenDlg.h:27
@ ASCII_OPEN_DLG_RGB32i
Definition: AsciiOpenDlg.h:39
@ ASCII_OPEN_DLG_Rf
Definition: AsciiOpenDlg.h:34
@ ASCII_OPEN_DLG_A
Definition: AsciiOpenDlg.h:33
@ ASCII_OPEN_DLG_Y
Definition: AsciiOpenDlg.h:25
@ ASCII_OPEN_DLG_RGB32f
Definition: AsciiOpenDlg.h:40
@ ASCII_OPEN_DLG_R
Definition: AsciiOpenDlg.h:30
@ ASCII_OPEN_DLG_NY
Definition: AsciiOpenDlg.h:28
@ ASCII_OPEN_DLG_Gf
Definition: AsciiOpenDlg.h:35
@ ASCII_OPEN_DLG_NZ
Definition: AsciiOpenDlg.h:29
@ ASCII_OPEN_DLG_B
Definition: AsciiOpenDlg.h:32
@ ASCII_OPEN_DLG_Af
Definition: AsciiOpenDlg.h:37
@ ASCII_OPEN_DLG_X
Definition: AsciiOpenDlg.h:24
@ ASCII_OPEN_DLG_Scalar
Definition: AsciiOpenDlg.h:42
@ ASCII_OPEN_DLG_G
Definition: AsciiOpenDlg.h:31
@ ASCII_OPEN_DLG_Bf
Definition: AsciiOpenDlg.h:36
@ ASCII_OPEN_DLG_Z
Definition: AsciiOpenDlg.h:26
@ ASCII_OPEN_DLG_None
Definition: AsciiOpenDlg.h:23
std::string filename
int count
char type
static QString A()
Definition: AsciiOpenDlg.h:59
static QString G()
Definition: AsciiOpenDlg.h:57
static QString Nz()
Definition: AsciiOpenDlg.h:55
static QString RGB32i()
Definition: AsciiOpenDlg.h:67
static QString RGB32f()
Definition: AsciiOpenDlg.h:68
static QString Bf()
Definition: AsciiOpenDlg.h:62
static QString Ny()
Definition: AsciiOpenDlg.h:54
static QString Z()
Definition: AsciiOpenDlg.h:52
static QString Nx()
Definition: AsciiOpenDlg.h:53
static QString Y()
Definition: AsciiOpenDlg.h:51
static QString Gf()
Definition: AsciiOpenDlg.h:61
static QString B()
Definition: AsciiOpenDlg.h:58
static QString X()
Definition: AsciiOpenDlg.h:50
static QString Af()
Definition: AsciiOpenDlg.h:63
static QString Grey()
Definition: AsciiOpenDlg.h:65
static QString Rf()
Definition: AsciiOpenDlg.h:60
static QString R()
Definition: AsciiOpenDlg.h:56
double m_averageLineSize
Definition: AsciiOpenDlg.h:207
Ui_AsciiOpenDialog * m_ui
Definition: AsciiOpenDlg.h:203
bool useCommaAsDecimal() const
Returns whether comma should be used as decimal point.
void checkSelectedColumnsValidity()
void setSkippedLines(int linesCount)
Sets the number of lines to skip.
unsigned char getSeparator() const
Returns user selected separator.
Definition: AsciiOpenDlg.h:146
void columnsTypeHasChanged(int index)
void commaDecimalCheckBoxToggled(bool)
Slot called when the 'comma as decimal' checkbox is toggled.
unsigned m_skippedLines
Definition: AsciiOpenDlg.h:205
void setInput(const QString &filename, QTextStream *stream=nullptr)
Sets the input filename or text stream.
QChar m_separator
Definition: AsciiOpenDlg.h:206
bool showLabelsIn2D() const
Whether labels should be visible in 2D.
std::vector< ColumnType > m_columnType
Identifies columns with numbers only [mandatory].
Definition: AsciiOpenDlg.h:215
unsigned getColumnsCount() const
Returns columns count per line.
Definition: AsciiOpenDlg.h:155
std::vector< SequenceItem > Sequence
ASCII open sequence.
Definition: AsciiOpenDlg.h:137
bool restorePreviousContext()
Restores the previous context ('Apply all' button)
void shortcutButtonPressed()
QTextStream * m_stream
Definition: AsciiOpenDlg.h:209
bool safeSequence() const
Returns whether the current sequence is 'safe'.
static void ResetApplyAll()
Resets the "apply all" flag (if set)
AsciiOpenDlg(QWidget *parent=nullptr)
Default constructor.
void autoFindBestSeparator()
Tries to guess the best separator automagically.
QString m_headerLine
Definition: AsciiOpenDlg.h:210
unsigned m_columnsCount
Definition: AsciiOpenDlg.h:217
static bool CheckOpenSequence(const Sequence &sequence, QString &errorMessage)
Checks the "opening" sequence as set by the user.
QString m_filename
Definition: AsciiOpenDlg.h:208
void setSeparator(QChar)
Sets the current separator.
Sequence getOpenSequence() const
Returns the whole "opening" sequence as set by the user.
~AsciiOpenDlg() override
Default destructor.
void onSeparatorChange(const QString &separator)
Slot called when separator changes.
void updateTable()
Forces the table to update itself.
unsigned getMaxCloudSize() const
Returns the max number of points per cloud.
const unsigned CC_MAX_NUMBER_OF_POINTS_PER_CLOUD
constexpr Qt::SplitBehavior SkipEmptyParts
Definition: QtCompat.h:302
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
Dialog 'context'.
AsciiOpenDlg::Sequence sequence
bool extractSFNameFrom1stLine
void load(Ui_AsciiOpenDialog *ui) const
Restores state.
void save(Ui_AsciiOpenDialog *ui)
Saves state.
AsciiOpenContext()
Default initializer.
double maxPointCountPerCloud
ASCII open sequence item.
Definition: AsciiOpenDlg.h:124
CC_ASCII_OPEN_DLG_TYPES type
Definition: AsciiOpenDlg.h:125