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:&nbsp;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:&nbsp;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:&nbsp;missing value" ).arg( title() ) );
1511     return error;
1512   }
1513 
1514   QFileInfo fi( path );
1515   if ( !fi.dir().exists() )
1516   {
1517     error.append( tr( "%1:&nbsp;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