@@ -110,14 +110,31 @@ Qgs3DMapCanvasWidget::Qgs3DMapCanvasWidget( const QString &name, bool isDocked )
110
110
mSpinChangeAttributeValue ->setShowClearButton ( false );
111
111
mPointCloudEditingToolbar ->addWidget ( new QLabel ( tr ( " Value" ) ) );
112
112
mSpinChangeAttributeValueAction = mPointCloudEditingToolbar ->addWidget ( mSpinChangeAttributeValue );
113
+ mSpinChangeAttributeValueAction ->setVisible ( false );
113
114
mCboChangeAttributeValue = new QComboBox ();
114
- mCboChangeAttributeValue ->setMaxVisibleItems ( 15 );
115
+ mCboChangeAttributeValue ->setEditable ( true );
116
+ mClassValidator = new ClassValidator ( this );
115
117
mCboChangeAttributeValueAction = mPointCloudEditingToolbar ->addWidget ( mCboChangeAttributeValue );
116
118
117
119
QAction *actionEditingToolbar = toolBar->addAction ( QIcon ( QgsApplication::iconPath ( " mIconPointCloudLayer.svg" ) ), tr ( " Show Editing Toolbar" ), this , [this ] { mEditingToolBar ->setVisible ( !mEditingToolBar ->isVisible () ); } );
118
120
actionEditingToolbar->setCheckable ( true );
119
121
connect ( mCboChangeAttribute , qOverload<int >( &QComboBox::currentIndexChanged ), this , [this ]( int ) { onPointCloudChangeAttributeSettingsChanged (); } );
120
- connect ( mCboChangeAttributeValue , qOverload<int >( &QComboBox::currentIndexChanged ), this , [this ]( int ) { mMapToolPointCloudChangeAttribute ->setNewValue ( mCboChangeAttributeValue ->currentData ().toDouble () ); } );
122
+ connect ( mCboChangeAttributeValue , qOverload<const QString &>( &QComboBox::currentTextChanged ), this , [this ]( const QString &text ) {
123
+ double newValue = 0 ;
124
+ if ( mCboChangeAttributeValue ->isEditable () )
125
+ {
126
+ const QStringList split = text.split ( ' ' );
127
+ if ( !split.isEmpty () )
128
+ {
129
+ newValue = split.constFirst ().toDouble ();
130
+ }
131
+ }
132
+ else
133
+ {
134
+ newValue = mCboChangeAttributeValue ->currentData ().toDouble ();
135
+ }
136
+ mMapToolPointCloudChangeAttribute ->setNewValue ( newValue );
137
+ } );
121
138
connect ( mSpinChangeAttributeValue , qOverload<double >( &QgsDoubleSpinBox::valueChanged ), this , [this ]( double ) { mMapToolPointCloudChangeAttribute ->setNewValue ( mSpinChangeAttributeValue ->value () ); } );
122
139
123
140
QAction *toggleOnScreenNavigation = toolBar->addAction (
@@ -903,10 +920,13 @@ void Qgs3DMapCanvasWidget::onPointCloudChangeAttributeSettingsChanged()
903
920
else if ( attributeName == QLatin1String ( " Synthetic" ) || attributeName == QLatin1String ( " KeyPoint" ) || attributeName == QLatin1String ( " Withheld" ) || attributeName == QLatin1String ( " Overlap" ) || attributeName == QLatin1String ( " ScanDirectionFlag" ) || attributeName == QLatin1String ( " EdgeOfFlightLine" ) )
904
921
{
905
922
useComboBox = true ;
923
+ const int oldIndex = mCboChangeAttributeValue ->currentIndex ();
906
924
QgsSignalBlocker< QComboBox > blocker ( mCboChangeAttributeValue );
907
925
mCboChangeAttributeValue ->clear ();
908
926
mCboChangeAttributeValue ->addItem ( tr ( " False" ), 0 );
909
927
mCboChangeAttributeValue ->addItem ( tr ( " True" ), 1 );
928
+ mCboChangeAttributeValue ->setEditable ( false );
929
+ mCboChangeAttributeValue ->setCurrentIndex ( std::min ( oldIndex, 1 ) );
910
930
}
911
931
else if ( attributeName == QLatin1String ( " ScannerChannel" ) )
912
932
{
@@ -917,13 +937,13 @@ void Qgs3DMapCanvasWidget::onPointCloudChangeAttributeSettingsChanged()
917
937
else if ( attributeName == QLatin1String ( " Classification" ) )
918
938
{
919
939
useComboBox = true ;
920
- const double oldValue = mCboChangeAttributeValue ->currentData ().toDouble ();
940
+ const QStringList split = mCboChangeAttributeValue ->currentText ().split ( ' ' );
941
+ const int oldValue = split.isEmpty () ? 0 : split.constFirst ().toInt ();
921
942
922
943
whileBlocking ( mCboChangeAttributeValue )->clear ();
923
- // Instead of showing a list of all available las codes, we are going to build a list of "most popular" and display it on top,
924
- // consisting of Classification renderer classes and used classes in the data from the layer's stats
925
- // Then the full list will go on after a separator.
926
- const QMap<int , QString> lasCodes = QgsPointCloudDataProvider::translatedLasClassificationCodes ();
944
+ // We will fill the combobox with all available classes from the Classification renderer (may have changed names) and the layer statistics
945
+ // Users will be able to manually type in any other class number too.
946
+ QMap<int , QString> lasCodes = QgsPointCloudDataProvider::translatedLasClassificationCodes ();
927
947
QMap<int , QString> classes;
928
948
929
949
QgsPointCloudLayer *layer = qobject_cast<QgsPointCloudLayer *>( QgisApp::instance ()->activeLayer () );
@@ -955,21 +975,23 @@ void Qgs3DMapCanvasWidget::onPointCloudChangeAttributeSettingsChanged()
955
975
}
956
976
for ( auto it = classes.constBegin (); it != classes.constEnd (); ++it )
957
977
{
978
+ // populate the combobox
958
979
whileBlocking ( mCboChangeAttributeValue )->addItem ( QStringLiteral ( " %1 (%2)" ).arg ( it.key () ).arg ( it.value () ), it.key () );
980
+ // and also update the labels in the full list of classes, which will be used in the editable combobox validator.
981
+ lasCodes[it.key ()] = it.value ();
959
982
}
960
983
}
961
- // after a separator, we add all the standard las classification codes 0-255 but we are keeping the classification renderer's labels
984
+ // new values (manually edited) will be added after a separator
962
985
mCboChangeAttributeValue ->insertSeparator ( mCboChangeAttributeValue ->count () );
963
- for ( auto it = lasCodes. constBegin (); it != lasCodes. constEnd (); ++it )
964
- {
965
- whileBlocking ( mCboChangeAttributeValue )-> addItem ( QStringLiteral ( " %1 (%2) " ). arg ( it. key () ). arg ( classes. value ( it. key (), it. value () ) ), it. key () );
966
- }
986
+ mClassValidator -> setClasses ( lasCodes );
987
+ mCboChangeAttributeValue -> setEditable ( true );
988
+ mCboChangeAttributeValue -> setValidator ( mClassValidator );
989
+ mCboChangeAttributeValue -> setCompleter ( nullptr );
967
990
968
991
// Try to reselect last selected value
969
992
for ( int i = 0 ; i < mCboChangeAttributeValue ->count (); ++i )
970
993
{
971
- bool ok = false ;
972
- if ( mCboChangeAttributeValue ->itemData ( i ).toDouble ( &ok ) == oldValue && ok )
994
+ if ( mCboChangeAttributeValue ->itemText ( i ).startsWith ( QStringLiteral ( " %1 " ).arg ( oldValue ) ) )
973
995
{
974
996
mCboChangeAttributeValue ->setCurrentIndex ( i );
975
997
break ;
@@ -987,7 +1009,7 @@ void Qgs3DMapCanvasWidget::onPointCloudChangeAttributeSettingsChanged()
987
1009
mSpinChangeAttributeValue ->setMinimum ( -180 );
988
1010
mSpinChangeAttributeValue ->setMaximum ( 180 );
989
1011
mSpinChangeAttributeValue ->setDecimals ( 3 );
990
- mSpinChangeAttributeValue ->setSuffix ( QStringLiteral ( " (%1) " ).arg ( tr ( " degrees" ) ) );
1012
+ mSpinChangeAttributeValue ->setSuffix ( QStringLiteral ( " %1 " ).arg ( tr ( " degrees" ) ) );
991
1013
}
992
1014
else if ( attributeName == QLatin1String ( " GpsTime" ) )
993
1015
{
@@ -997,13 +1019,28 @@ void Qgs3DMapCanvasWidget::onPointCloudChangeAttributeSettingsChanged()
997
1019
}
998
1020
999
1021
mMapToolPointCloudChangeAttribute ->setAttribute ( attributeName );
1000
- mMapToolPointCloudChangeAttribute ->setNewValue ( useComboBox ? mCboChangeAttributeValue ->currentData ().toDouble () : mSpinChangeAttributeValue ->value () );
1022
+ double newValue = 0 ;
1023
+ if ( useComboBox && mCboChangeAttributeValue ->isEditable () )
1024
+ {
1025
+ // read class integer
1026
+ const QStringList split = mCboChangeAttributeValue ->currentText ().split ( ' ' );
1027
+ if ( !split.isEmpty () )
1028
+ newValue = split.constFirst ().toDouble ();
1029
+ }
1030
+ else if ( useComboBox )
1031
+ {
1032
+ // read true/false combo box
1033
+ newValue = mCboChangeAttributeValue ->currentData ().toDouble ();
1034
+ }
1035
+ else
1036
+ {
1037
+ // read the spinbox value
1038
+ newValue = mSpinChangeAttributeValue ->value ();
1039
+ }
1040
+ mMapToolPointCloudChangeAttribute ->setNewValue ( newValue );
1001
1041
1002
1042
mCboChangeAttributeValueAction ->setVisible ( useComboBox );
1003
1043
mSpinChangeAttributeValueAction ->setVisible ( !useComboBox );
1004
-
1005
- mCboChangeAttributeValue ->setEditable ( true );
1006
- mCboChangeAttributeValue ->lineEdit ()->setReadOnly ( true );
1007
1044
}
1008
1045
1009
1046
void Qgs3DMapCanvasWidget::setSceneExtentOn2DCanvas ()
@@ -1030,3 +1067,44 @@ void Qgs3DMapCanvasWidget::setSceneExtent( const QgsRectangle &extent )
1030
1067
else
1031
1068
mMainCanvas ->unsetMapTool ( mMapToolExtent .get () );
1032
1069
}
1070
+
1071
+ ClassValidator::ClassValidator ( QWidget *parent )
1072
+ : QValidator( parent )
1073
+ {
1074
+ mRx = QRegularExpression ( QStringLiteral ( " ([0-9]{1,3})" ) );
1075
+ }
1076
+
1077
+ QValidator::State ClassValidator::validate ( QString &input, int &pos ) const
1078
+ {
1079
+ QRegularExpressionMatch match = mRx .match ( input );
1080
+ const QString number = match.captured ();
1081
+ bool ok;
1082
+ const int n = number.toInt ( &ok );
1083
+
1084
+ if ( !ok && pos == 0 )
1085
+ {
1086
+ input.clear ();
1087
+ return QValidator::State::Intermediate;
1088
+ }
1089
+
1090
+ if ( !ok )
1091
+ return QValidator::State::Invalid;
1092
+ if ( n < 0 || n > 255 )
1093
+ return QValidator::State::Invalid;
1094
+ if ( mClasses .contains ( n ) )
1095
+ {
1096
+ input = QStringLiteral ( " %1 (%2)" ).arg ( n ).arg ( mClasses [n] );
1097
+ pos = std::min ( pos, number.size () );
1098
+ return QValidator::State::Acceptable;
1099
+ }
1100
+ return QValidator::State::Intermediate;
1101
+ }
1102
+
1103
+ void ClassValidator::fixup ( QString &input ) const
1104
+ {
1105
+ QRegularExpressionMatch match = mRx .match ( input );
1106
+ const QString number = match.captured ();
1107
+ bool ok;
1108
+ const int n = number.toInt ( &ok );
1109
+ input = QStringLiteral ( " %1 (%2)" ).arg ( n ).arg ( mClasses [n] );
1110
+ }
0 commit comments