1 /***************************************************************************
2 qgsgrassmoduleparam.cpp
3 -------------------
4 begin : August, 2015
5 copyright : (C) 2015 by Radim Blazek
6 email : radim.blazek@gmail.com
7 ***************************************************************************/
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include <QFileDialog>
18 #include <QFileInfo>
19 #include <QMessageBox>
20 #include "qgssettings.h"
21
22 #include "qgis.h"
23 #include "qgsdatasourceuri.h"
24 #include "qgslogger.h"
25 #include "qgsmaplayer.h"
26 #include "qgsproject.h"
27 #include "qgsrasterlayer.h"
28 #include "qgsvectorlayer.h"
29
30 #include "qgsgrass.h"
31 #include "qgsgrassfeatureiterator.h"
32 #include "qgsgrassmodule.h"
33 #include "qgsgrassmoduleinput.h"
34 #include "qgsgrassmoduleparam.h"
35 #include "qgsgrassplugin.h"
36 #include "qgsgrassprovider.h"
37
38 #if 0
39 extern "C"
40 {
41 #include <grass/vector.h>
42 }
43 #endif
44
45 /********************** QgsGrassModuleParam *************************/
QgsGrassModuleParam(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct)46 QgsGrassModuleParam::QgsGrassModuleParam( QgsGrassModule *module, QString key,
47 QDomElement &qdesc, QDomElement &gdesc, QDomNode &gnode, bool direct )
48 : mModule( module )
49 , mKey( key )
50 , mMultiple( false )
51 , mHidden( false )
52 , mRequired( false )
53 , mDirect( direct )
54 {
55 Q_UNUSED( gdesc )
56 //mAnswer = qdesc.attribute("answer", "");
57
58 if ( !qdesc.attribute( QStringLiteral( "answer" ) ).isNull() )
59 {
60 mAnswer = qdesc.attribute( QStringLiteral( "answer" ) ).trimmed();
61 }
62 else
63 {
64 QDomNode n = gnode.namedItem( QStringLiteral( "default" ) );
65 if ( !n.isNull() )
66 {
67 QDomElement e = n.toElement();
68 mAnswer = e.text().trimmed();
69 }
70 }
71
72 if ( qdesc.attribute( QStringLiteral( "hidden" ) ) == QLatin1String( "yes" ) )
73 {
74 mHidden = true;
75 }
76
77 QString label, description;
78 if ( !qdesc.attribute( QStringLiteral( "label" ) ).isEmpty() )
79 {
80 label = QApplication::translate( "grasslabel", qdesc.attribute( QStringLiteral( "label" ) ).trimmed().toUtf8() );
81 }
82 if ( label.isEmpty() )
83 {
84 QDomNode n = gnode.namedItem( QStringLiteral( "label" ) );
85 if ( !n.isNull() )
86 {
87 QDomElement e = n.toElement();
88 label = module->translate( e.text() );
89 }
90 }
91 QDomNode n = gnode.namedItem( QStringLiteral( "description" ) );
92 if ( !n.isNull() )
93 {
94 QDomElement e = n.toElement();
95 description = module->translate( e.text() );
96 }
97
98 if ( !label.isEmpty() )
99 {
100 mTitle = label;
101 mToolTip = description;
102 }
103 else
104 {
105 mTitle = description;
106 }
107
108 mRequired = gnode.toElement().attribute( QStringLiteral( "required" ) ) == QLatin1String( "yes" );
109
110 mMultiple = gnode.toElement().attribute( QStringLiteral( "multiple" ) ) == QLatin1String( "yes" );
111
112 mId = qdesc.attribute( QStringLiteral( "id" ) );
113 }
114
hidden()115 bool QgsGrassModuleParam::hidden()
116 {
117 return mHidden;
118 }
119
options()120 QStringList QgsGrassModuleParam::options()
121 {
122 return QStringList();
123 }
124
getDescPrompt(QDomElement descDomElement,const QString & name)125 QString QgsGrassModuleParam::getDescPrompt( QDomElement descDomElement, const QString &name )
126 {
127 QDomNode gispromptNode = descDomElement.namedItem( QStringLiteral( "gisprompt" ) );
128
129 if ( !gispromptNode.isNull() )
130 {
131 QDomElement gispromptElement = gispromptNode.toElement();
132 if ( !gispromptElement.isNull() )
133 {
134 return gispromptElement.attribute( name );
135 }
136 }
137 return QString();
138 }
139
nodeByKey(QDomElement descDomElement,QString key)140 QDomNode QgsGrassModuleParam::nodeByKey( QDomElement descDomElement, QString key )
141 {
142 QgsDebugMsg( "called with key=" + key );
143 QDomNode n = descDomElement.firstChild();
144
145 while ( !n.isNull() )
146 {
147 QDomElement e = n.toElement();
148
149 if ( !e.isNull() )
150 {
151 if ( e.tagName() == QLatin1String( "parameter" ) || e.tagName() == QLatin1String( "flag" ) )
152 {
153 if ( e.attribute( QStringLiteral( "name" ) ) == key )
154 {
155 return n;
156 }
157 }
158 }
159 n = n.nextSibling();
160 }
161
162 return QDomNode();
163 }
164
nodesByType(QDomElement descDomElement,STD_OPT optionType,const QString & age)165 QList<QDomNode> QgsGrassModuleParam::nodesByType( QDomElement descDomElement, STD_OPT optionType, const QString &age )
166 {
167 // TODO: never tested
168 QList<QDomNode> nodes;
169
170 // Not all options have prompt set, for example G_OPT_V_TYPE and G_OPT_V_FIELD, which would be useful, don't have prompt
171 QMap<QString, STD_OPT> typeMap;
172 typeMap.insert( QStringLiteral( "dbtable" ), G_OPT_DB_TABLE );
173 typeMap.insert( QStringLiteral( "dbdriver" ), G_OPT_DB_DRIVER );
174 typeMap.insert( QStringLiteral( "dbname" ), G_OPT_DB_DATABASE );
175 typeMap.insert( QStringLiteral( "dbcolumn" ), G_OPT_DB_COLUMN );
176 typeMap.insert( QStringLiteral( "vector" ), G_OPT_V_INPUT );
177
178 QDomNode n = descDomElement.firstChild();
179
180 while ( !n.isNull() )
181 {
182 QString prompt = getDescPrompt( n.toElement(), QStringLiteral( "prompt" ) );
183 if ( typeMap.value( prompt ) == optionType )
184 {
185 if ( age.isEmpty() || getDescPrompt( n.toElement(), QStringLiteral( "age" ) ) == age )
186 {
187 nodes << n;
188 }
189 }
190
191 n = n.nextSibling();
192 }
193
194 return nodes;
195 }
196
197 /***************** QgsGrassModuleGroupBoxItem *********************/
198
QgsGrassModuleGroupBoxItem(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)199 QgsGrassModuleGroupBoxItem::QgsGrassModuleGroupBoxItem( QgsGrassModule *module, QString key,
200 QDomElement &qdesc, QDomElement &gdesc, QDomNode &gnode,
201 bool direct, QWidget *parent )
202 : QGroupBox( parent )
203 , QgsGrassModuleParam( module, key, qdesc, gdesc, gnode, direct )
204 {
205 adjustTitle();
206 setToolTip( mToolTip );
207 }
208
resizeEvent(QResizeEvent * event)209 void QgsGrassModuleGroupBoxItem::resizeEvent( QResizeEvent *event )
210 {
211 Q_UNUSED( event )
212 adjustTitle();
213 setToolTip( mToolTip );
214 }
215
adjustTitle()216 void QgsGrassModuleGroupBoxItem::adjustTitle()
217 {
218 QString tit = fontMetrics().elidedText( mTitle, Qt::ElideRight, width() - 20 );
219
220 setTitle( tit );
221 }
222
223 /***************** QgsGrassModuleMultiParam *********************/
224
QgsGrassModuleMultiParam(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)225 QgsGrassModuleMultiParam::QgsGrassModuleMultiParam( QgsGrassModule *module, QString key,
226 QDomElement &qdesc, QDomElement &gdesc, QDomNode &gnode,
227 bool direct, QWidget *parent )
228 : QgsGrassModuleGroupBoxItem( module, key, qdesc, gdesc, gnode, direct, parent )
229 {
230 adjustTitle();
231 setToolTip( mToolTip );
232
233 // variable number of line edits
234 // add/delete buttons for multiple options
235 mLayout = new QHBoxLayout( this );
236 mParamsLayout = new QVBoxLayout();
237
238 mLayout->insertLayout( -1, mParamsLayout );
239
240 }
241
showAddRemoveButtons()242 void QgsGrassModuleMultiParam::showAddRemoveButtons()
243 {
244 mButtonsLayout = new QVBoxLayout();
245 mLayout->insertLayout( -1, mButtonsLayout );
246
247 // TODO: how to keep both buttons on the top?
248 QPushButton *addButton = new QPushButton( QStringLiteral( "+" ), this );
249 connect( addButton, &QAbstractButton::clicked, this, &QgsGrassModuleMultiParam::addRow );
250 mButtonsLayout->addWidget( addButton, 0, Qt::AlignTop );
251
252 QPushButton *removeButton = new QPushButton( QStringLiteral( "-" ), this );
253 connect( removeButton, &QAbstractButton::clicked, this, &QgsGrassModuleMultiParam::removeRow );
254 mButtonsLayout->addWidget( removeButton, 0, Qt::AlignTop );
255
256 // Don't enable this, it makes the group box expanding
257 // mButtonsLayout->addStretch();
258 }
259
260 /********************** QgsGrassModuleOption *************************/
261
QgsGrassModuleOption(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)262 QgsGrassModuleOption::QgsGrassModuleOption( QgsGrassModule *module, QString key,
263 QDomElement &qdesc, QDomElement &gdesc, QDomNode &gnode,
264 bool direct, QWidget *parent )
265 : QgsGrassModuleMultiParam( module, key, qdesc, gdesc, gnode, direct, parent )
266 , mControlType( NoControl )
267 , mValueType( String )
268 , mOutputType( None )
269 , mHaveLimits( false )
270 , mMin( std::numeric_limits<int>::max() )
271 , mMax( std::numeric_limits<int>::min() )
272 , mIsOutput( false )
273 , mUsesRegion( false )
274 {
275 setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
276
277 if ( mHidden )
278 {
279 hide();
280 }
281
282 // Is it output?
283 QDomNode promptNode = gnode.namedItem( QStringLiteral( "gisprompt" ) );
284 if ( !promptNode.isNull() )
285 {
286 QDomElement promptElem = promptNode.toElement();
287 QString element = promptElem.attribute( QStringLiteral( "element" ) );
288 QString age = promptElem.attribute( QStringLiteral( "age" ) );
289
290 if ( age == QLatin1String( "new" ) )
291 {
292 mOutputElement = element;
293 mIsOutput = true;
294
295 if ( element == QLatin1String( "vector" ) )
296 {
297 mOutputType = Vector;
298 }
299 else if ( element == QLatin1String( "cell" ) )
300 {
301 mOutputType = Raster;
302 }
303 }
304 }
305
306 // String without options
307 if ( !mHidden )
308 {
309 QDomElement gelem = gnode.toElement();
310
311 // Output option may have missing gisprompt if output may be both vector and raster according to other options (e.g. v.kernel)
312 // outputType qgm attribute allows forcing an output type
313
314 // Predefined values ?
315 QDomNode valuesNode = gnode.namedItem( QStringLiteral( "values" ) );
316 QDomElement valuesElem = valuesNode.toElement(); // null if valuesNode is null
317
318 if ( !valuesNode.isNull() && valuesNode.childNodes().count() > 1 )
319 {
320 // predefined values -> ComboBox or CheckBox
321
322 // TODO: add add/removeRow support for ComboBox?
323
324 // one or many?
325 if ( gelem.attribute( QStringLiteral( "multiple" ) ) == QLatin1String( "yes" ) )
326 {
327 mControlType = CheckBoxes;
328 }
329 else
330 {
331 mControlType = ComboBox;
332 mComboBox = new QComboBox( this );
333 paramsLayout()->addWidget( mComboBox );
334 }
335
336 // List of values to be excluded
337 QStringList exclude = qdesc.attribute( QStringLiteral( "exclude" ) ).split( ',', QString::SkipEmptyParts );
338
339 QDomNode valueNode = valuesElem.firstChild();
340
341 while ( !valueNode.isNull() )
342 {
343 QDomElement valueElem = valueNode.toElement();
344
345 if ( !valueElem.isNull() && valueElem.tagName() == QLatin1String( "value" ) )
346 {
347
348 QDomNode n = valueNode.namedItem( QStringLiteral( "name" ) );
349 if ( !n.isNull() )
350 {
351 QDomElement e = n.toElement();
352 QString val = e.text().trimmed();
353
354 if ( exclude.contains( val ) == 0 )
355 {
356 n = valueNode.namedItem( QStringLiteral( "description" ) );
357 QString desc;
358 if ( !n.isNull() )
359 {
360 e = n.toElement();
361 desc = e.text().trimmed();
362 }
363 else
364 {
365 desc = val;
366 }
367 desc.replace( 0, 1, desc.at( 0 ).toUpper() );
368
369 if ( mControlType == ComboBox )
370 {
371 mComboBox->addItem( desc );
372 if ( mAnswer.length() > 0 && val == mAnswer )
373 {
374 mComboBox->setCurrentIndex( mComboBox->count() - 1 );
375 }
376 }
377 else
378 {
379 QgsGrassModuleCheckBox *cb = new QgsGrassModuleCheckBox( desc, this );
380 mCheckBoxes.push_back( cb );
381 paramsLayout()->addWidget( cb );
382 }
383
384 mValues.push_back( val );
385 }
386 }
387 }
388
389 valueNode = valueNode.nextSibling();
390 }
391 }
392 else // No values
393 {
394 // Line edit
395 mControlType = LineEdit;
396
397 // Output option may have missing gisprompt if output may be both vector and raster according to other options (e.g. v.kernel)
398 // outputType qgm attribute allow forcing an output type
399 QgsDebugMsg( "outputType = " + qdesc.attribute( "outputType" ) );
400 if ( qdesc.hasAttribute( QStringLiteral( "outputType" ) ) )
401 {
402 QString outputType = qdesc.attribute( QStringLiteral( "outputType" ) );
403 mIsOutput = true;
404 if ( outputType == QLatin1String( "vector" ) )
405 {
406 mOutputElement = QStringLiteral( "vector" );
407 mOutputType = Vector;
408 }
409 else if ( outputType == QLatin1String( "raster" ) )
410 {
411 mOutputElement = QStringLiteral( "cell" );
412 mOutputType = Raster;
413 }
414 else
415 {
416 mErrors << tr( "Unknown outputType" ) + " : " + outputType;
417 }
418 }
419
420 if ( gelem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "integer" ) )
421 {
422 mValueType = Integer;
423 }
424 else if ( gelem.attribute( QStringLiteral( "type" ) ) == QLatin1String( "float" ) )
425 {
426 mValueType = Double;
427 }
428
429 QStringList minMax;
430 if ( valuesNode.childNodes().count() == 1 )
431 {
432 QDomNode valueNode = valuesElem.firstChild();
433
434 QDomNode n = valueNode.namedItem( QStringLiteral( "name" ) );
435 if ( !n.isNull() )
436 {
437 QDomElement e = n.toElement();
438 QString val = e.text().trimmed();
439 minMax = val.split( '-' );
440 if ( minMax.size() == 2 )
441 {
442 mHaveLimits = true;
443 mMin = minMax.at( 0 ).toDouble();
444 mMax = minMax.at( 1 ).toDouble();
445 }
446 }
447 }
448
449 QDomNode keydescNode = gnode.namedItem( QStringLiteral( "keydesc" ) );
450 if ( !keydescNode.isNull() )
451 {
452 // fixed number of line edits
453 // Example:
454 // <keydesc>
455 // <item order="1">rows</item>
456 // <item order="2">columns</item>
457 // </keydesc>
458
459 QDomNodeList keydescs = keydescNode.childNodes();
460 for ( int k = 0; k < keydescs.count(); k++ )
461 {
462 QDomNode nodeItem = keydescs.at( k );
463 QString itemDesc = nodeItem.toElement().text().trimmed();
464 //QString itemDesc = nodeItem.firstChild().toText().data();
465 QgsDebugMsg( "keydesc item = " + itemDesc );
466
467 addRow();
468 }
469 }
470 else
471 {
472 addRow();
473 if ( gelem.attribute( QStringLiteral( "multiple" ) ) == QLatin1String( "yes" ) )
474 {
475 showAddRemoveButtons();
476 }
477 }
478 }
479 }
480
481 mUsesRegion = false;
482 QString region = qdesc.attribute( QStringLiteral( "region" ) );
483 if ( region.length() > 0 )
484 {
485 if ( region == QLatin1String( "yes" ) )
486 mUsesRegion = true;
487 }
488 else
489 {
490 QgsDebugMsg( "\n\n\n\n**************************" );
491 QgsDebugMsg( QString( "isOutput = %1" ).arg( isOutput() ) );
492 QgsDebugMsg( QString( "mOutputType = %1" ).arg( mOutputType ) );
493 if ( isOutput() && mOutputType == Raster )
494 mUsesRegion = true;
495 }
496 QgsDebugMsg( QString( "mUsesRegion = %1" ).arg( mUsesRegion ) );
497 }
498
addRow()499 void QgsGrassModuleOption::addRow()
500 {
501
502 // TODO make the widget growing with new lines. HOW???!!!
503 QLineEdit *lineEdit = new QLineEdit( this );
504 mLineEdits << lineEdit;
505 lineEdit->setText( mAnswer );
506
507 if ( mValueType == Integer )
508 {
509 if ( mHaveLimits )
510 {
511 mValidator = new QIntValidator( static_cast<int>( mMin ), static_cast<int>( mMax ), this );
512 }
513 else
514 {
515 mValidator = new QIntValidator( this );
516 }
517 lineEdit->setValidator( mValidator );
518 }
519 else if ( mValueType == Double )
520 {
521 if ( mHaveLimits )
522 {
523 mValidator = new QDoubleValidator( mMin, mMax, 10, this );
524 }
525 else
526 {
527 mValidator = new QDoubleValidator( this );
528 }
529 lineEdit->setValidator( mValidator );
530 }
531 else if ( mIsOutput )
532 {
533 QRegExp rx;
534 if ( mOutputType == Vector )
535 {
536 rx.setPattern( QStringLiteral( "[A-Za-z_][A-Za-z0-9_]+" ) );
537 }
538 else
539 {
540 rx.setPattern( QStringLiteral( "[A-Za-z0-9_.]+" ) );
541 }
542 mValidator = new QRegExpValidator( rx, this );
543
544 lineEdit->setValidator( mValidator );
545 }
546
547 if ( mIsOutput && mDirect )
548 {
549 QHBoxLayout *l = new QHBoxLayout();
550 l->addWidget( lineEdit );
551 lineEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
552 QPushButton *button = new QPushButton( tr( "Browse" ) );
553 l->addWidget( button );
554 paramsLayout()->addItem( l );
555 connect( button, &QAbstractButton::clicked, this, &QgsGrassModuleOption::browse );
556 }
557 else
558 {
559 paramsLayout()->addWidget( lineEdit );
560 }
561 }
562
removeRow()563 void QgsGrassModuleOption::removeRow()
564 {
565
566 if ( mLineEdits.size() < 2 )
567 {
568 return;
569 }
570 delete mLineEdits.at( mLineEdits.size() - 1 );
571 mLineEdits.removeLast();
572 }
573
browse(bool checked)574 void QgsGrassModuleOption::browse( bool checked )
575 {
576 Q_UNUSED( checked )
577
578 QgsSettings settings;
579 QString lastDir = settings.value( QStringLiteral( "GRASS/lastDirectOutputDir" ), QString() ).toString();
580 QString fileName = QFileDialog::getSaveFileName( this, tr( "Output file" ), lastDir, tr( "GeoTIFF" ) + " (*.tif)" );
581 if ( !fileName.isEmpty() )
582 {
583 if ( !fileName.endsWith( QLatin1String( ".tif" ), Qt::CaseInsensitive ) && !fileName.endsWith( QLatin1String( ".tiff" ), Qt::CaseInsensitive ) )
584 {
585 fileName = fileName + ".tif";
586 }
587 mLineEdits.at( 0 )->setText( fileName );
588 settings.setValue( QStringLiteral( "GRASS/lastDirectOutputDir" ), QFileInfo( fileName ).absolutePath() );
589 }
590 }
591
outputExists()592 QString QgsGrassModuleOption::outputExists()
593 {
594
595 if ( !mIsOutput )
596 return QString();
597
598 QLineEdit *lineEdit = mLineEdits.at( 0 );
599 QString value = lineEdit->text().trimmed();
600 QgsDebugMsg( "mKey = " + mKey );
601 QgsDebugMsg( "value = " + value );
602 QgsDebugMsg( "mOutputElement = " + mOutputElement );
603
604 if ( value.length() == 0 )
605 return QString();
606
607 QString path = QgsGrass::getDefaultGisdbase() + "/"
608 + QgsGrass::getDefaultLocation() + "/"
609 + QgsGrass::getDefaultMapset() + "/"
610 + mOutputElement + "/" + value;
611
612 QFileInfo fi( path );
613
614 if ( fi.exists() )
615 {
616 return ( lineEdit->text() );
617 }
618
619 return QString();
620 }
621
value()622 QString QgsGrassModuleOption::value()
623 {
624 QString value;
625
626 if ( mHidden )
627 {
628 return mAnswer;
629 }
630 else if ( mControlType == LineEdit )
631 {
632 for ( int i = 0; i < mLineEdits.size(); i++ )
633 {
634 QLineEdit *lineEdit = mLineEdits.at( i );
635 if ( lineEdit->text().trimmed().length() > 0 )
636 {
637 if ( value.length() > 0 )
638 value.append( "," );
639 value.append( lineEdit->text().trimmed() );
640 }
641 }
642 }
643 else if ( mControlType == ComboBox )
644 {
645 value = mValues[mComboBox->currentIndex()];
646 }
647 else if ( mControlType == CheckBoxes )
648 {
649 QStringList values;
650 for ( int i = 0; i < mCheckBoxes.size(); ++i )
651 {
652 if ( mCheckBoxes[i]->isChecked() )
653 {
654 values.append( mValues[i] );
655 }
656 }
657 value = values.join( QLatin1Char( ',' ) );
658 }
659 return value;
660 }
661
checkVersion(const QString & version_min,const QString & version_max,QStringList & errors)662 bool QgsGrassModuleOption::checkVersion( const QString &version_min, const QString &version_max, QStringList &errors )
663 {
664 QgsDebugMsg( "version_min = " + version_min );
665 QgsDebugMsg( "version_max = " + version_max );
666
667 bool minOk = true;
668 bool maxOk = true;
669 QRegExp rxVersionMajor( "(\\d+)" );
670 QRegExp rxVersion( "(\\d+)\\.(\\d+)" );
671 if ( !version_min.isEmpty() )
672 {
673 if ( rxVersion.exactMatch( version_min ) )
674 {
675 int versionMajorMin = rxVersion.cap( 1 ).toInt();
676 int versionMinorMin = rxVersion.cap( 2 ).toInt();
677 if ( QgsGrass::versionMajor() < versionMajorMin || ( QgsGrass::versionMajor() == versionMajorMin && QgsGrass::versionMinor() < versionMinorMin ) )
678 {
679 minOk = false;
680 }
681 }
682 else if ( rxVersionMajor.exactMatch( version_min ) )
683 {
684 int versionMajorMin = rxVersionMajor.cap( 1 ).toInt();
685 if ( QgsGrass::versionMajor() < versionMajorMin )
686 {
687 minOk = false;
688 }
689 }
690 else
691 {
692 errors << tr( "Cannot parse version_min %1" ).arg( version_min );
693 }
694 }
695
696 if ( !version_max.isEmpty() )
697 {
698 if ( rxVersion.exactMatch( version_max ) )
699 {
700 int versionMajorMax = rxVersion.cap( 1 ).toInt();
701 int versionMinorMax = rxVersion.cap( 2 ).toInt();
702 if ( QgsGrass::versionMajor() > versionMajorMax || ( QgsGrass::versionMajor() == versionMajorMax && QgsGrass::versionMinor() > versionMinorMax ) )
703 {
704 maxOk = false;
705 }
706 }
707 else if ( rxVersionMajor.exactMatch( version_max ) )
708 {
709 int versionMajorMax = rxVersionMajor.cap( 1 ).toInt();
710 if ( QgsGrass::versionMajor() > versionMajorMax )
711 {
712 maxOk = false;
713 }
714 }
715 else
716 {
717 errors << tr( "Cannot parse version_max %1" ).arg( version_max );
718 }
719 }
720 return errors.isEmpty() && minOk && maxOk;
721 }
722
options()723 QStringList QgsGrassModuleOption::options()
724 {
725 QStringList list;
726
727 QString val = value();
728 if ( !val.isEmpty() )
729 {
730 list.push_back( mKey + "=" + val );
731 }
732
733 return list;
734 }
735
ready()736 QString QgsGrassModuleOption::ready()
737 {
738 QgsDebugMsg( "key = " + key() );
739
740 QString error;
741
742 if ( value().isEmpty() && mRequired )
743 {
744 error.append( tr( "%1: missing value" ).arg( title() ) );
745 }
746
747 return error;
748 }
749
750 /***************** QgsGrassModuleFlag *********************/
QgsGrassModuleFlag(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)751 QgsGrassModuleFlag::QgsGrassModuleFlag( QgsGrassModule *module, QString key,
752 QDomElement &qdesc, QDomElement &gdesc, QDomNode &gnode,
753 bool direct, QWidget *parent )
754 : QgsGrassModuleCheckBox( QString(), parent ), QgsGrassModuleParam( module, key, qdesc, gdesc, gnode, direct )
755 {
756
757 if ( mHidden )
758 hide();
759
760 if ( mAnswer == QLatin1String( "on" ) )
761 setChecked( true );
762 else
763 setChecked( false );
764
765 setText( mTitle );
766 setToolTip( mToolTip );
767 }
768
options()769 QStringList QgsGrassModuleFlag::options()
770 {
771 QStringList list;
772 if ( isChecked() )
773 {
774 list.push_back( "-" + mKey );
775 }
776 return list;
777 }
778
779 /***************** QgsGrassModuleGdalInput *********************/
780
QgsGrassModuleGdalInput(QgsGrassModule * module,Type type,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)781 QgsGrassModuleGdalInput::QgsGrassModuleGdalInput(
782 QgsGrassModule *module, Type type, QString key, QDomElement &qdesc,
783 QDomElement &gdesc, QDomNode &gnode, bool direct, QWidget *parent )
784 : QgsGrassModuleGroupBoxItem( module, key, qdesc, gdesc, gnode, direct, parent )
785 , mType( type )
786 {
787 if ( mTitle.isEmpty() )
788 {
789 mTitle = tr( "OGR/PostGIS/GDAL Input" );
790 }
791 adjustTitle();
792
793 // Check if this parameter is required
794 mRequired = gnode.toElement().attribute( QStringLiteral( "required" ) ) == QLatin1String( "yes" );
795
796 // Read "layeroption" is defined
797 QString opt = qdesc.attribute( QStringLiteral( "layeroption" ) );
798 if ( ! opt.isNull() )
799 {
800
801 QDomNode optNode = nodeByKey( gdesc, opt );
802
803 if ( optNode.isNull() )
804 {
805 mErrors << tr( "Cannot find layeroption %1" ).arg( opt );
806 }
807 else
808 {
809 mOgrLayerOption = opt;
810 }
811 }
812
813 // Read "whereoption" if defined
814 opt = qdesc.attribute( QStringLiteral( "whereoption" ) );
815 if ( !opt.isNull() )
816 {
817 QDomNode optNode = nodeByKey( gdesc, opt );
818 if ( optNode.isNull() )
819 {
820 mErrors << tr( "Cannot find whereoption %1" ).arg( opt );
821 }
822 else
823 {
824 mOgrWhereOption = opt;
825 }
826 }
827
828 QVBoxLayout *l = new QVBoxLayout( this );
829 mLayerComboBox = new QComboBox();
830 mLayerComboBox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy:: Preferred );
831 l->addWidget( mLayerComboBox );
832
833 QLabel *lbl = new QLabel( tr( "Password" ) );
834 l->addWidget( lbl );
835
836 mLayerPassword = new QLineEdit();
837 mLayerPassword->setEchoMode( QLineEdit::Password );
838 mLayerPassword->setEnabled( false );
839 l->addWidget( mLayerPassword );
840
841 lbl->setBuddy( mLayerPassword );
842
843 connect( QgsProject::instance(), &QgsProject::layersAdded,
844 this, &QgsGrassModuleGdalInput::updateQgisLayers );
845 connect( QgsProject::instance(), &QgsProject::layersRemoved,
846 this, &QgsGrassModuleGdalInput::updateQgisLayers );
847
848 // Fill in QGIS layers
849 updateQgisLayers();
850 }
851
updateQgisLayers()852 void QgsGrassModuleGdalInput::updateQgisLayers()
853 {
854
855 QString current = mLayerComboBox->currentText();
856 mLayerComboBox->clear();
857 mUri.clear();
858 mOgrLayers.clear();
859
860 // If not required, add an empty item to combobox and a padding item into
861 // layer containers.
862 if ( !mRequired )
863 {
864 mUri.push_back( QString() );
865 mOgrLayers.push_back( QString() );
866 mOgrWheres.push_back( QString() );
867 mLayerComboBox->addItem( tr( "Select a layer" ), QVariant() );
868 }
869
870 Q_FOREACH ( QgsMapLayer *layer, QgsProject::instance()->mapLayers().values() )
871 {
872 if ( !layer ) continue;
873
874 if ( mType == Ogr && layer->type() == QgsMapLayerType::VectorLayer )
875 {
876 QgsVectorLayer *vector = qobject_cast<QgsVectorLayer *>( layer );
877 if ( !vector ||
878 ( vector->providerType() != QLatin1String( "ogr" ) && vector->providerType() != QLatin1String( "postgres" ) )
879 )
880 continue;
881
882 QgsDataProvider *provider = vector->dataProvider();
883
884 QString uri;
885 QString ogrLayer;
886 QString ogrWhere;
887 if ( vector->providerType() == QLatin1String( "postgres" ) )
888 {
889 // Construct OGR DSN
890 QgsDataSourceUri dsUri( provider->dataSourceUri() );
891 uri = "PG:" + dsUri.connectionInfo();
892
893 // Starting with GDAL 1.7.0, it is possible to restrict the schemas
894 // layer names are then listed without schema if only one schema is specified
895 if ( !dsUri.schema().isEmpty() )
896 {
897 uri += " schemas=" + dsUri.schema();
898 }
899
900 ogrLayer += dsUri.table();
901 ogrWhere = dsUri.sql();
902 }
903 else if ( vector->providerType() == QLatin1String( "ogr" ) )
904 {
905 QStringList items = provider->dataSourceUri().split( '|' );
906
907 if ( items.size() > 1 )
908 {
909 uri = items[0];
910
911 ogrLayer.clear();
912 ogrWhere.clear();
913
914 for ( int i = 1; i < items.size(); i++ )
915 {
916 QStringList args = items[i].split( '=' );
917
918 if ( args.size() != 2 )
919 continue;
920
921 if ( args[0] == QLatin1String( "layername" ) && args[0] == QLatin1String( "layerid" ) )
922 {
923 ogrLayer = args[1];
924 }
925 else if ( args[0] == QLatin1String( "subset" ) )
926 {
927 ogrWhere = args[1];
928 }
929 }
930
931 if ( uri.endsWith( QLatin1String( ".shp" ), Qt::CaseInsensitive ) )
932 {
933 ogrLayer.clear();
934 }
935 }
936 else
937 {
938 uri = items[0];
939 ogrLayer.clear();
940 ogrWhere.clear();
941 }
942 }
943
944 QgsDebugMsg( "uri = " + uri );
945 QgsDebugMsg( "ogrLayer = " + ogrLayer );
946
947 mLayerComboBox->addItem( layer->name() );
948 if ( layer->name() == current )
949 mLayerComboBox->setItemText( mLayerComboBox->currentIndex(), current );
950
951 mUri.push_back( uri );
952 mOgrLayers.push_back( ogrLayer );
953 mOgrWheres.push_back( ogrWhere );
954 }
955 else if ( mType == Gdal && layer->type() == QgsMapLayerType::RasterLayer )
956 {
957 QString uri = layer->source();
958 mLayerComboBox->addItem( layer->name() );
959 if ( layer->name() == current )
960 mLayerComboBox->setItemText( mLayerComboBox->currentIndex(), current );
961 mUri.push_back( uri );
962 mOgrLayers.push_back( QString() );
963 mOgrWheres.push_back( QString() );
964 }
965 }
966 }
967
options()968 QStringList QgsGrassModuleGdalInput::options()
969 {
970 QStringList list;
971
972 int current = mLayerComboBox->currentIndex();
973 if ( current < 0 )
974 return list;
975
976 QString opt( mKey + "=" );
977
978 if ( current >= 0 && current < mUri.size() )
979 {
980 QString uri = mUri[current];
981
982 if ( uri.startsWith( QLatin1String( "PG:" ) ) && uri.contains( QLatin1String( "password=" ) ) && !mLayerPassword->text().isEmpty() )
983 {
984 uri += " password=" + mLayerPassword->text();
985 }
986
987 opt.append( uri );
988 }
989
990 list.push_back( opt );
991
992 if ( !mOgrLayerOption.isEmpty() && mOgrLayers[current].size() > 0 )
993 {
994 opt = mOgrLayerOption + "=";
995 opt += mOgrLayers[current];
996 list.push_back( opt );
997 }
998
999 if ( !mOgrWhereOption.isEmpty() && mOgrWheres[current].length() > 0 )
1000 {
1001 list.push_back( mOgrWhereOption + "=" + mOgrWheres[current] );
1002 }
1003
1004 return list;
1005 }
1006
ready()1007 QString QgsGrassModuleGdalInput::ready()
1008 {
1009
1010 QString error;
1011
1012 QgsDebugMsg( QString( "count = %1" ).arg( mLayerComboBox->count() ) );
1013 if ( mLayerComboBox->count() == 0 )
1014 {
1015 error.append( tr( "%1: no input" ).arg( title() ) );
1016 }
1017 return error;
1018 }
1019
changed(int i)1020 void QgsGrassModuleGdalInput::changed( int i )
1021 {
1022 mLayerPassword->setEnabled( i < mUri.size() && mUri.value( i ).startsWith( QLatin1String( "PG:" ) ) && !mUri.value( i ).contains( QLatin1String( "password=" ) ) );
1023 }
1024
1025 /***************** QgsGrassModuleField *********************/
QgsGrassModuleField(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)1026 QgsGrassModuleField::QgsGrassModuleField( QgsGrassModule *module, QString key,
1027 QDomElement &qdesc, QDomElement &gdesc, QDomNode &gnode, bool direct, QWidget *parent )
1028 : QgsGrassModuleOption( module, key, qdesc, gdesc, gnode, direct, parent )
1029 {
1030 // Validator is disabled to also allow entering of expressions
1031 #if 0
1032 QRegExp rx( "^[a-zA-Z_][a-zA-Z0-9_]*$" );
1033 Q_FOREACH ( QLineEdit *lineEdit, mLineEdits )
1034 {
1035 lineEdit->setValidator( new QRegExpValidator( rx, this ) );
1036 }
1037 #endif
1038 }
1039
1040 /***************** QgsGrassModuleVectorField *********************/
1041
QgsGrassModuleVectorField(QgsGrassModule * module,QgsGrassModuleStandardOptions * options,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)1042 QgsGrassModuleVectorField::QgsGrassModuleVectorField(
1043 QgsGrassModule *module, QgsGrassModuleStandardOptions *options,
1044 QString key, QDomElement &qdesc,
1045 QDomElement &gdesc, QDomNode &gnode, bool direct, QWidget *parent )
1046 : QgsGrassModuleMultiParam( module, key, qdesc, gdesc, gnode, direct, parent )
1047 , mModuleStandardOptions( options )
1048 {
1049 if ( mTitle.isEmpty() )
1050 {
1051 mTitle = tr( "Attribute field" );
1052 }
1053 adjustTitle();
1054
1055 QDomNode promptNode = gnode.namedItem( QStringLiteral( "gisprompt" ) );
1056 QDomElement gelem = gnode.toElement();
1057
1058 mType = qdesc.attribute( QStringLiteral( "type" ) );
1059
1060 mLayerKey = qdesc.attribute( QStringLiteral( "layer" ) );
1061 if ( mLayerKey.isNull() || mLayerKey.length() == 0 )
1062 {
1063 mErrors << tr( "'layer' attribute in field tag with key= %1 is missing." ).arg( mKey );
1064 }
1065 else
1066 {
1067 QgsGrassModuleParam *item = mModuleStandardOptions->itemByKey( mLayerKey );
1068 // TODO check type
1069 if ( item )
1070 {
1071 mLayerInput = dynamic_cast<QgsGrassModuleInput *>( item );
1072 connect( mLayerInput, &QgsGrassModuleInput::valueChanged, this, &QgsGrassModuleVectorField::updateFields );
1073 }
1074 }
1075
1076 addRow();
1077 if ( gelem.attribute( QStringLiteral( "multiple" ) ) == QLatin1String( "yes" ) )
1078 {
1079 showAddRemoveButtons();
1080 }
1081
1082 // Fill in layer current fields
1083 updateFields();
1084 }
1085
addRow()1086 void QgsGrassModuleVectorField::addRow()
1087 {
1088 QComboBox *comboBox = new QComboBox();
1089 comboBox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
1090 paramsLayout()->addWidget( comboBox );
1091 mComboBoxList << comboBox;
1092 updateFields();
1093 }
1094
removeRow()1095 void QgsGrassModuleVectorField::removeRow()
1096 {
1097
1098 if ( mComboBoxList.size() < 2 )
1099 {
1100 return;
1101 }
1102 delete mComboBoxList.at( mComboBoxList.size() - 1 );
1103 mComboBoxList.removeLast();
1104 }
1105
updateFields()1106 void QgsGrassModuleVectorField::updateFields()
1107 {
1108
1109 Q_FOREACH ( QComboBox *comboBox, mComboBoxList )
1110 {
1111 QString current = comboBox->currentText();
1112 comboBox->clear();
1113
1114 if ( mLayerInput == nullptr )
1115 {
1116 continue;
1117 }
1118
1119 int index = 0;
1120 Q_FOREACH ( const QgsField &field, mLayerInput->currentFields() )
1121 {
1122 if ( mType.contains( field.typeName() ) )
1123 {
1124 comboBox->addItem( field.name() );
1125 QgsDebugMsg( "current = " + current + " field = " + field.name() );
1126 if ( field.name() == current )
1127 {
1128 comboBox->setCurrentIndex( index );
1129 }
1130 index++;
1131 }
1132 }
1133 }
1134 }
1135
options()1136 QStringList QgsGrassModuleVectorField::options()
1137 {
1138 QStringList list;
1139
1140 QStringList valueList;
1141 Q_FOREACH ( QComboBox *comboBox, mComboBoxList )
1142 {
1143 if ( !comboBox->currentText().isEmpty() )
1144 {
1145 valueList << comboBox->currentText();
1146 }
1147 }
1148
1149 if ( !valueList.isEmpty() )
1150 {
1151 QString opt = mKey + "=" + valueList.join( QLatin1Char( ',' ) );
1152 list << opt;
1153 }
1154
1155 return list;
1156 }
1157
1158 /***************** QgsGrassModuleSelection *********************/
1159
QgsGrassModuleSelection(QgsGrassModule * module,QgsGrassModuleStandardOptions * options,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)1160 QgsGrassModuleSelection::QgsGrassModuleSelection(
1161 QgsGrassModule *module, QgsGrassModuleStandardOptions *options,
1162 QString key, QDomElement &qdesc,
1163 QDomElement &gdesc, QDomNode &gnode, bool direct, QWidget *parent )
1164 : QgsGrassModuleGroupBoxItem( module, key, qdesc, gdesc, gnode, direct, parent )
1165 , mModuleStandardOptions( options )
1166 {
1167 if ( mTitle.isEmpty() )
1168 {
1169 mTitle = tr( "Selected categories" );
1170 }
1171 adjustTitle();
1172
1173 QDomNode promptNode = gnode.namedItem( QStringLiteral( "gisprompt" ) );
1174 QDomElement promptElem = promptNode.toElement();
1175
1176 mLayerId = qdesc.attribute( QStringLiteral( "layerid" ) );
1177
1178 mType = qdesc.attribute( QStringLiteral( "type" ) );
1179
1180 QgsGrassModuleParam *item = mModuleStandardOptions->item( mLayerId );
1181 // TODO check type
1182 if ( item )
1183 {
1184 mLayerInput = dynamic_cast<QgsGrassModuleInput *>( item );
1185 connect( mLayerInput, &QgsGrassModuleInput::valueChanged, this, &QgsGrassModuleSelection::onLayerChanged );
1186 }
1187
1188 QHBoxLayout *l = new QHBoxLayout( this );
1189 mLineEdit = new QLineEdit( this );
1190 l->addWidget( mLineEdit );
1191
1192 mModeComboBox = new QComboBox( this );
1193 mModeComboBox->setSizeAdjustPolicy( QComboBox::AdjustToContents );
1194 mModeComboBox->addItem( tr( "Manual entry" ), Manual );
1195 connect( mModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGrassModuleSelection::onModeChanged );
1196 l->addWidget( mModeComboBox );
1197
1198 connect( QgsProject::instance(), &QgsProject::layersAdded, this, &QgsGrassModuleSelection::onLayerChanged );
1199 connect( QgsProject::instance(), &QgsProject::layersRemoved, this, &QgsGrassModuleSelection::onLayerChanged );
1200
1201 // Fill in layer current fields
1202 onLayerChanged();
1203 }
1204
onLayerChanged()1205 void QgsGrassModuleSelection::onLayerChanged()
1206 {
1207
1208 if ( !mLayerInput )
1209 {
1210 return;
1211 }
1212
1213 QStringList layerIds;
1214 // add new layers matching selected input layer if not yet present
1215 Q_FOREACH ( QgsMapLayer *layer, QgsProject::instance()->mapLayers().values() )
1216 {
1217 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
1218 if ( vectorLayer && vectorLayer->providerType() == QLatin1String( "grass" ) )
1219 {
1220 QString uri = vectorLayer->dataProvider()->dataSourceUri();
1221 QgsDebugMsg( "uri = " + uri );
1222 QString layerCode = uri.split( '/' ).last();
1223 if ( mLayerInput->currentLayerCodes().contains( layerCode ) )
1224 {
1225 // Qt::UserRole+1 may be also uri (AddLayer) but hardly matching layer id
1226 if ( mModeComboBox->findData( vectorLayer->id(), Qt::UserRole + 1 ) == -1 )
1227 {
1228 mModeComboBox->addItem( vectorLayer->name() + " " + tr( "layer selection" ), Layer );
1229 mModeComboBox->setItemData( mModeComboBox->count() - 1, vectorLayer->id(), Qt::UserRole + 1 );
1230 }
1231 layerIds << vectorLayer->id();
1232 }
1233 }
1234 }
1235 // remove layers no more present
1236 for ( int i = mModeComboBox->count() - 1; i >= 0; i-- )
1237 {
1238 if ( mModeComboBox->itemData( i ).toInt() != Layer )
1239 {
1240 continue;
1241 }
1242 QString id = mModeComboBox->itemData( i, Qt::UserRole + 1 ).toString();
1243 if ( !layerIds.contains( id ) )
1244 {
1245 mModeComboBox->removeItem( i );
1246 }
1247 }
1248
1249 // clear old AddLayer
1250 for ( int i = mModeComboBox->count() - 1; i >= 0; i-- )
1251 {
1252 if ( mModeComboBox->itemData( i ).toInt() == AddLayer )
1253 {
1254 mModeComboBox->removeItem( i );
1255 }
1256 }
1257
1258 if ( layerIds.size() == 0 ) // non of selected layer is in canvas
1259 {
1260 Q_FOREACH ( QString layerCode, mLayerInput->currentLayerCodes() )
1261 {
1262 if ( mLayerInput->currentLayer() )
1263 {
1264 mModeComboBox->addItem( tr( "Add to canvas layer" ) + " " + mLayerInput->currentMap() + " " + layerCode, AddLayer );
1265 QgsGrassObject grassObject = mLayerInput->currentLayer()->grassObject();
1266 QString uri = grassObject.mapsetPath() + "/" + grassObject.name() + "/" + layerCode;
1267 QgsDebugMsg( "uri = " + uri );
1268 // Qt::UserRole+1 may be also layer id (Layer) but hardly matching layer uri
1269 if ( mModeComboBox->findData( uri, Qt::UserRole + 1 ) == -1 )
1270 {
1271 mModeComboBox->setItemData( mModeComboBox->count() - 1, uri, Qt::UserRole + 1 );
1272 QString name = grassObject.name() + " " + layerCode;
1273 mModeComboBox->setItemData( mModeComboBox->count() - 1, name, Qt::UserRole + 2 );
1274 }
1275 }
1276 }
1277 }
1278 }
1279
currentSelectionLayerId()1280 QString QgsGrassModuleSelection::currentSelectionLayerId()
1281 {
1282 QString id;
1283 int index = mModeComboBox->currentIndex();
1284 if ( mModeComboBox->itemData( index ).toInt() == Layer )
1285 {
1286 id = mModeComboBox->itemData( index, Qt::UserRole + 1 ).toString();
1287 }
1288 return id;
1289 }
1290
currentSelectionLayer()1291 QgsVectorLayer *QgsGrassModuleSelection::currentSelectionLayer()
1292 {
1293 QString id = currentSelectionLayerId();
1294 if ( id.isEmpty() )
1295 {
1296 return nullptr;
1297 }
1298 QgsMapLayer *layer = QgsProject::instance()->mapLayer( id );
1299 return qobject_cast<QgsVectorLayer *>( layer );
1300 }
1301
onModeChanged()1302 void QgsGrassModuleSelection::onModeChanged()
1303 {
1304 int index = mModeComboBox->currentIndex();
1305 if ( mModeComboBox->itemData( index ).toInt() == AddLayer )
1306 {
1307 QString uri = mModeComboBox->itemData( index, Qt::UserRole + 1 ).toString();
1308 QString name = mModeComboBox->itemData( index, Qt::UserRole + 2 ).toString();
1309 QgsDebugMsg( "uri = " + uri );
1310
1311 QgsVectorLayer *layer = new QgsVectorLayer( uri, name, QStringLiteral( "grass" ) );
1312 QgsProject::instance()->addMapLayer( layer );
1313 onLayerChanged(); // update with added layer
1314 }
1315 else if ( mModeComboBox->itemData( index ).toInt() == Layer )
1316 {
1317 QString id = mModeComboBox->itemData( index, Qt::UserRole + 1 ).toString();
1318 QgsMapLayer *layer = QgsProject::instance()->mapLayer( id );
1319 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
1320 if ( vectorLayer )
1321 {
1322 onLayerSelectionChanged();
1323 connect( vectorLayer, &QgsVectorLayer::selectionChanged, this, &QgsGrassModuleSelection::onLayerSelectionChanged );
1324 }
1325 }
1326 }
1327
onLayerSelectionChanged()1328 void QgsGrassModuleSelection::onLayerSelectionChanged()
1329 {
1330 mLineEdit->clear();
1331
1332 QgsVectorLayer *vectorLayer = currentSelectionLayer();
1333 if ( !vectorLayer )
1334 {
1335 return;
1336 }
1337
1338 QList<int> cats;
1339 Q_FOREACH ( QgsFeatureId fid, vectorLayer->selectedFeatureIds() )
1340 {
1341 cats << QgsGrassFeatureIterator::catFromFid( fid );
1342 }
1343 std::sort( cats.begin(), cats.end() );
1344 QString list;
1345 // make ranges of cats
1346 int last = -1;
1347 int range = false;
1348 Q_FOREACH ( int cat, cats )
1349 {
1350 if ( cat == 0 )
1351 {
1352 continue;
1353 }
1354 if ( last == cat - 1 ) // begin or continue range
1355 {
1356 range = true;
1357 }
1358 else if ( range ) // close range and next cat
1359 {
1360 list += QStringLiteral( "-%1,%2" ).arg( last ).arg( cat );
1361 range = false;
1362 }
1363 else // next cat
1364 {
1365 if ( !list.isEmpty() )
1366 {
1367 list += QLatin1Char( ',' );
1368 }
1369 list += QString::number( cat );
1370 }
1371 last = cat;
1372 }
1373 if ( range )
1374 {
1375 list += QStringLiteral( "-%1" ).arg( last );
1376 }
1377
1378 mLineEdit->setText( list );
1379 }
1380
options()1381 QStringList QgsGrassModuleSelection::options()
1382 {
1383 QStringList list;
1384
1385 if ( !mLineEdit->text().isEmpty() )
1386 {
1387 QString opt( mKey + "=" + mLineEdit->text() );
1388 list.push_back( opt );
1389 }
1390
1391 return list;
1392 }
1393
1394 /***************** QgsGrassModuleFile *********************/
1395
QgsGrassModuleFile(QgsGrassModule * module,QString key,QDomElement & qdesc,QDomElement & gdesc,QDomNode & gnode,bool direct,QWidget * parent)1396 QgsGrassModuleFile::QgsGrassModuleFile(
1397 QgsGrassModule *module,
1398 QString key, QDomElement &qdesc,
1399 QDomElement &gdesc, QDomNode &gnode, bool direct, QWidget *parent )
1400 : QgsGrassModuleGroupBoxItem( module, key, qdesc, gdesc, gnode, direct, parent )
1401 , mType( Old )
1402 {
1403 if ( mTitle.isEmpty() )
1404 {
1405 mTitle = tr( "File" );
1406 }
1407 adjustTitle();
1408
1409 if ( qdesc.attribute( QStringLiteral( "type" ) ).toLower() == QLatin1String( "new" ) )
1410 {
1411 mType = New;
1412 }
1413 if ( qdesc.attribute( QStringLiteral( "type" ) ).toLower() == QLatin1String( "multiple" ) )
1414 {
1415 mType = Multiple;
1416 }
1417
1418 if ( qdesc.attribute( QStringLiteral( "type" ) ).toLower() == QLatin1String( "directory" ) )
1419 {
1420 mType = Directory;
1421 }
1422
1423 mFilters = qdesc.attribute( QStringLiteral( "filters" ) );
1424
1425 mFileOption = qdesc.attribute( QStringLiteral( "fileoption" ) );
1426
1427 QHBoxLayout *l = new QHBoxLayout( this );
1428 mLineEdit = new QLineEdit();
1429 mBrowseButton = new QPushButton( QStringLiteral( "…" ) );
1430 l->addWidget( mLineEdit );
1431 l->addWidget( mBrowseButton );
1432
1433 connect( mBrowseButton, &QAbstractButton::clicked,
1434 this, &QgsGrassModuleFile::browse );
1435 }
1436
options()1437 QStringList QgsGrassModuleFile::options()
1438 {
1439 QStringList list;
1440 QString path = mLineEdit->text().trimmed();
1441
1442 if ( mFileOption.isNull() )
1443 {
1444 QString opt( mKey + "=" + path );
1445 list.push_back( opt );
1446 }
1447 else
1448 {
1449 QFileInfo fi( path );
1450
1451 QString opt( mKey + "=" + fi.path() );
1452 list.push_back( opt );
1453
1454 opt = mFileOption + "=" + fi.baseName();
1455 list.push_back( opt );
1456 }
1457
1458 return list;
1459 }
1460
browse()1461 void QgsGrassModuleFile::browse()
1462 {
1463 static QString lastDir = QDir::currentPath();
1464
1465 if ( mType == Multiple )
1466 {
1467 QString path = mLineEdit->text().split( ',' ).first();
1468 if ( path.isEmpty() )
1469 path = lastDir;
1470 else
1471 path = QFileInfo( path ).absolutePath();
1472
1473 QStringList files = QFileDialog::getOpenFileNames( this, nullptr, path, mFilters );
1474 if ( files.isEmpty() )
1475 return;
1476
1477 lastDir = QFileInfo( files[0] ).absolutePath();
1478
1479 mLineEdit->setText( files.join( QLatin1Char( ',' ) ) );
1480 }
1481 else
1482 {
1483 QString selectedFile = mLineEdit->text();
1484 if ( selectedFile.isEmpty() )
1485 selectedFile = lastDir;
1486
1487 if ( mType == New )
1488 selectedFile = QFileDialog::getSaveFileName( this, nullptr, selectedFile, mFilters );
1489 else if ( mType == Directory )
1490 selectedFile = QFileDialog::getExistingDirectory( this, nullptr, selectedFile );
1491 else
1492 selectedFile = QFileDialog::getOpenFileName( this, nullptr, selectedFile, mFilters );
1493
1494 lastDir = QFileInfo( selectedFile ).absolutePath();
1495
1496 mLineEdit->setText( selectedFile );
1497 }
1498 }
1499
ready()1500 QString QgsGrassModuleFile::ready()
1501 {
1502 QgsDebugMsg( "key = " + key() );
1503
1504 QString error;
1505 QString path = mLineEdit->text().trimmed();
1506
1507
1508 if ( path.length() == 0 && mRequired )
1509 {
1510 error.append( tr( "%1: missing value" ).arg( title() ) );
1511 return error;
1512 }
1513
1514 QFileInfo fi( path );
1515 if ( !fi.dir().exists() )
1516 {
1517 error.append( tr( "%1: directory does not exist" ).arg( title() ) );
1518 }
1519
1520 return error;
1521 }
1522
1523 /***************************** QgsGrassModuleCheckBox *********************************/
1524
QgsGrassModuleCheckBox(const QString & text,QWidget * parent)1525 QgsGrassModuleCheckBox::QgsGrassModuleCheckBox( const QString &text, QWidget *parent )
1526 : QCheckBox( text, parent )
1527 , mText( text )
1528 {
1529 adjustText();
1530 }
1531
resizeEvent(QResizeEvent * event)1532 void QgsGrassModuleCheckBox::resizeEvent( QResizeEvent *event )
1533 {
1534 Q_UNUSED( event )
1535 adjustText();
1536 }
setText(const QString & text)1537 void QgsGrassModuleCheckBox::setText( const QString &text )
1538 {
1539 mText = text;
1540 adjustText();
1541 }
setToolTip(const QString & text)1542 void QgsGrassModuleCheckBox::setToolTip( const QString &text )
1543 {
1544 mTip = text;
1545 QWidget::setToolTip( text );
1546 }
adjustText()1547 void QgsGrassModuleCheckBox::adjustText()
1548 {
1549 QString t = fontMetrics().elidedText( mText, Qt::ElideRight, width() - iconSize().width() - 20 );
1550 QCheckBox::setText( t );
1551
1552 if ( mTip.isEmpty() )
1553 {
1554 QString tt;
1555 if ( t != mText )
1556 {
1557 tt = mText;
1558 }
1559 QWidget::setToolTip( tt );
1560 }
1561 }
1562
1563