1 /***************************************************************************
2                               qgsgrassmodule.cpp
3                              -------------------
4     begin                : March, 2005
5     copyright            : (C) 2005 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 #include "qgsgrassmodule.h"
17 #include "qgsgrassmapcalc.h"
18 #include "qgsgrassplugin.h"
19 #include "qgsgrassselect.h"
20 #include "qgsgrasstools.h"
21 #include "qgsgrassutils.h"
22 #include "qgsgrass.h"
23 #include "qgsconfig.h"
24 
25 #include "qgisinterface.h"
26 #include "qgsapplication.h"
27 #include "qgscoordinatereferencesystem.h"
28 #include "qgscoordinatetransform.h"
29 
30 #include "qgslogger.h"
31 #include "qgsmapcanvas.h"
32 
33 #include <typeinfo>
34 #include <QDomDocument>
35 #include <QMessageBox>
36 #include <QSvgRenderer>
37 
38 extern "C"
39 {
40 #include <grass/glocale.h>
41 }
42 
execArguments(QString module)43 QStringList QgsGrassModule::execArguments( QString module )
44 {
45   QString exe;
46   QStringList arguments;
47 
48   exe = QgsGrass::findModule( module );
49   if ( exe.isNull() )
50   {
51     return arguments;
52   }
53 
54 #ifdef Q_OS_WIN
55   if ( exe.endsWith( ".py" ) )
56   {
57     arguments.append( "python" );
58   }
59 #endif
60 
61   arguments.append( exe );
62 
63   return arguments;
64 }
65 
processEnvironment(bool direct)66 QProcessEnvironment QgsGrassModule::processEnvironment( bool direct )
67 {
68   QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
69 
70   QStringList paths = QgsGrass::grassModulesPaths();
71   paths += environment.value( QStringLiteral( "PATH" ) ).split( QgsGrass::pathSeparator() );
72   environment.insert( QStringLiteral( "PATH" ), paths.join( QgsGrass::pathSeparator() ) );
73   environment.insert( QStringLiteral( "PYTHONPATH" ), QgsGrass::getPythonPath() );
74 
75   if ( direct )
76   {
77     // Set path to GRASS gis fake library
78     QgsGrassModule::setDirectLibraryPath( environment );
79     environment.insert( QStringLiteral( "QGIS_PREFIX_PATH" ), QgsApplication::prefixPath() );
80     // Window to avoid crash in G__gisinit
81     environment.insert( QStringLiteral( "GRASS_REGION" ), QStringLiteral( "west:0;south:0;east:1;north:1;cols:1;rows:1;proj:0;zone:0" ) );
82   }
83   return environment;
84 }
85 
QgsGrassModule(QgsGrassTools * tools,QString moduleName,QgisInterface * iface,bool direct,QWidget * parent,Qt::WindowFlags f)86 QgsGrassModule::QgsGrassModule( QgsGrassTools *tools, QString moduleName, QgisInterface *iface,
87                                 bool direct, QWidget *parent, Qt::WindowFlags f )
88   : QWidget( parent, f )
89   , QgsGrassModuleBase()
90   , mSuccess( false )
91   , mDirect( direct )
92 {
93   Q_UNUSED( f )
94   QgsDebugMsg( "called" );
95 
96   setupUi( this );
97   connect( mRunButton, &QPushButton::clicked, this, &QgsGrassModule::mRunButton_clicked );
98   connect( mCloseButton, &QPushButton::clicked, this, &QgsGrassModule::mCloseButton_clicked );
99   connect( mViewButton, &QPushButton::clicked, this, &QgsGrassModule::mViewButton_clicked );
100   // use fixed width font because module's output may be formatted
101   mOutputTextBrowser->setStyleSheet( QStringLiteral( "font-family: Monospace; font-size: 9pt;" ) );
102   lblModuleName->setText( tr( "Module: %1" ).arg( moduleName ) );
103   mTools = tools;
104   mIface = iface;
105   mCanvas = mIface->mapCanvas();
106   //mParent = parent;
107 
108   /* Read module description and create options */
109 
110   // Open QGIS module description
111   QString mpath = QgsGrass::modulesConfigDirPath() + "/" + moduleName + ".qgm";
112   QgsDebugMsg( QString( "mpath = %1" ).arg( mpath ) );
113   QFile qFile( mpath );
114   if ( !qFile.exists() )
115   {
116     mErrors.append( tr( "The module file (%1) not found." ).arg( mpath ) );
117     return;
118   }
119   if ( ! qFile.open( QIODevice::ReadOnly ) )
120   {
121     mErrors.append( tr( "Cannot open module file (%1)" ).arg( mpath ) );
122     return;
123   }
124   QDomDocument qDoc( QStringLiteral( "qgisgrassmodule" ) );
125   QString err;
126   int line, column;
127   if ( !qDoc.setContent( &qFile,  &err, &line, &column ) )
128   {
129     QString errmsg = tr( "Cannot read module file (%1)" ).arg( mpath )
130                      + tr( "\n%1\nat line %2 column %3" ).arg( err ).arg( line ).arg( column );
131     QgsDebugMsg( errmsg );
132     mErrors.append( errmsg );
133     qFile.close();
134     return;
135   }
136   qFile.close();
137   QDomElement qDocElem = qDoc.documentElement();
138 
139   // Read GRASS module description
140   QString xName = qDocElem.attribute( QStringLiteral( "module" ) );
141   QString xDocName = qDocElem.attribute( QStringLiteral( "manual" ) );
142   if ( xDocName.isEmpty() )
143   {
144     xDocName = xName;
145   }
146 
147   // Binary modules on windows has .exe extension
148   // but not all modules have to be binary (can be scripts)
149   // => test if the module is in path and if it is not
150   // add .exe and test again
151 #ifdef Q_OS_WIN
152   mXName = QgsGrass::findModule( xName );
153   if ( mXName.isNull() )
154   {
155     QgsDebugMsg( "Module " + xName + " not found" );
156     mErrors.append( tr( "Module %1 not found" ).arg( xName ) );
157     return;
158   }
159 #else
160   mXName = xName;
161 #endif
162 
163   QVBoxLayout *layout = new QVBoxLayout( mTabWidget->widget( 0 ) );
164   layout->setContentsMargins( 0, 0, 0, 0 );
165   if ( xName == QLatin1String( "r.mapcalc" ) )
166   {
167     mOptions = new QgsGrassMapcalc( mTools, this,
168                                     mIface, mTabWidget->widget( 0 ) );
169   }
170   else
171   {
172     mOptions = new QgsGrassModuleStandardOptions( mTools, this,
173         mIface, mXName, qDocElem, mDirect, mTabWidget->widget( 0 ) );
174   }
175   layout->addWidget( dynamic_cast<QWidget *>( mOptions ) );
176 
177   if ( !mOptions->errors().isEmpty() )
178   {
179     mErrors.append( mOptions->errors() );
180   }
181 
182   // Hide display if there is no output
183   if ( !mOptions->hasOutput( QgsGrassModuleOption::Vector )
184        && !mOptions->hasOutput( QgsGrassModuleOption::Raster ) )
185   {
186     mViewButton->hide();
187   }
188   mViewButton->setEnabled( false );
189 
190   // Create manual if available
191   QString gisBase = getenv( "GISBASE" );
192   QString manPath = gisBase + "/docs/html/" + xDocName + ".html";
193   QFile manFile( manPath );
194   if ( manFile.exists() )
195   {
196     mManualTextBrowser->setOpenExternalLinks( true );
197     mManualTextBrowser->setSource( QUrl::fromLocalFile( manPath ) );
198   }
199   else
200   {
201     mManualTextBrowser->clear();
202     mManualTextBrowser->textCursor().insertImage( QStringLiteral( ":/grass/error.png" ) );
203     mManualTextBrowser->insertPlainText( tr( "Cannot find man page %1" ).arg( manPath ) );
204     mManualTextBrowser->insertPlainText( tr( "Please ensure you have the GRASS documentation installed." ) );
205   }
206 
207   connect( &mProcess, &QProcess::readyReadStandardOutput, this, &QgsGrassModule::readStdout );
208   connect( &mProcess, &QProcess::readyReadStandardError, this, &QgsGrassModule::readStderr );
209   connect( &mProcess, static_cast<void ( QProcess::* )( int, QProcess::ExitStatus )>( &QProcess::finished ), this, &QgsGrassModule::finished );
210 
211   const char *env = "GRASS_MESSAGE_FORMAT=gui";
212   char *envstr = new char[strlen( env ) + 1];
213   strcpy( envstr, env );
214   putenv( envstr );
215 
216   mOutputTextBrowser->setReadOnly( true );
217 }
218 
description(QString path)219 QgsGrassModule::Description QgsGrassModule::description( QString path )
220 {
221   QgsDebugMsg( "called." );
222 
223   // Open QGIS module description
224   path.append( ".qgm" );
225   QFile qFile( path );
226   if ( !qFile.exists() )
227   {
228     return Description( tr( "Not available, description not found (%1)" ).arg( path ) );
229   }
230   if ( ! qFile.open( QIODevice::ReadOnly ) )
231   {
232     return Description( tr( "Not available, cannot open description (%1)" ).arg( path ) );
233   }
234   QDomDocument qDoc( QStringLiteral( "qgisgrassmodule" ) );
235   QString err;
236   int line, column;
237   if ( !qDoc.setContent( &qFile,  &err, &line, &column ) )
238   {
239     QString errmsg = tr( "Cannot read module file (%1)" ).arg( path )
240                      + tr( "\n%1\nat line %2 column %3" ).arg( err ).arg( line ).arg( column );
241     QgsDebugMsg( errmsg );
242     QMessageBox::warning( nullptr, tr( "Warning" ), errmsg );
243     qFile.close();
244     return Description( tr( "Not available, incorrect description (%1)" ).arg( path ) );
245   }
246   qFile.close();
247   QDomElement qDocElem = qDoc.documentElement();
248 
249   QString label = QApplication::translate( "grasslabel", qDocElem.attribute( QStringLiteral( "label" ) ).trimmed().toUtf8() );
250   bool direct = qDocElem.attribute( QStringLiteral( "direct" ) ) == QLatin1String( "1" );
251   return Description( label, direct );
252 }
253 
label(QString path)254 QString QgsGrassModule::label( QString path )
255 {
256   return description( path ).label;
257 }
258 
pixmap(QString path,int height)259 QPixmap QgsGrassModule::pixmap( QString path, int height )
260 {
261   //QgsDebugMsg( QString( "path = %1" ).arg( path ) );
262 
263   QList<QPixmap> pixmaps;
264 
265   // Create vector of available pictures
266   int cnt = 1;
267   for ( ;; )
268   {
269     // SVG
270     QString fpath = path + "." + QString::number( cnt ) + ".svg";
271     QFileInfo fi( fpath );
272     if ( fi.exists() )
273     {
274       QSvgRenderer pic;
275       if ( ! pic.load( fpath ) )
276         break;
277 
278       QRect br( QPoint( 0, 0 ), pic.defaultSize() );
279 
280       double scale = 1. * height / br.height();
281 
282       int width = ( int )( scale * br.width() );
283       if ( width <= 0 )
284         width = height; // should not happen
285       QPixmap pixmap( width, height );
286       pixmap.fill( Qt::transparent );
287       //pixmap.fill( QColor( 255, 255, 255 ) );
288       QPainter painter( &pixmap );
289       painter.setRenderHint( QPainter::Antialiasing );
290 
291       pic.render( &painter );
292       painter.end();
293 
294       pixmaps << pixmap;
295     }
296     else // PNG
297     {
298       fpath = path + "." + QString::number( cnt ) + ".png";
299       fi.setFile( fpath );
300 
301       if ( !fi.exists() )
302         break;
303 
304       QPixmap pixmap;
305 
306       if ( ! pixmap.load( fpath, "PNG" ) )
307         break;
308 
309       double scale = 1. * height / pixmap.height();
310       int width = ( int )( scale * pixmap.width() );
311 
312       QImage img = pixmap.toImage();
313       img = img.scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
314       pixmap = QPixmap::fromImage( img );
315 
316       pixmaps.push_back( pixmap );
317     }
318     cnt++;
319   }
320 
321   if ( pixmaps.isEmpty() )
322   {
323     return QPixmap();
324   }
325 
326   // Get total width
327   int width = 0;
328   for ( int i = 0; i < pixmaps.size(); i++ )
329   {
330     width += pixmaps[i].width();
331   }
332 
333   if ( width <= 0 )
334     width = height; //should not happen
335 
336   QString iconsPath = QgsApplication::pkgDataPath() + "/grass/modules/";
337   QFileInfo iconsfi( iconsPath );
338 
339   int plusWidth = 8;
340   int arrowWidth = 9;
341 
342   QString arrowPath = iconsPath + "grass_arrow.png";
343   QPixmap arrowPixmap;
344   iconsfi.setFile( arrowPath );
345   if ( iconsfi.exists() && arrowPixmap.load( arrowPath, "PNG" ) )
346   {
347     double scale = 1. * height / arrowPixmap.height();
348     arrowWidth = ( int )( scale * arrowPixmap.width() );
349 
350     QImage img = arrowPixmap.toImage();
351     img = img.scaled( arrowWidth, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
352     arrowPixmap = QPixmap::fromImage( img );
353   }
354 #if 0
355   if ( iconsfi.exists() )
356   {
357     QSvgRenderer pic;
358     if ( pic.load( arrowPath ) )
359     {
360       QRect br( QPoint( 0, 0 ), pic.defaultSize() );
361 
362       double scale = 1. * height / br.height();
363 
364       arrowWidth = ( int )( scale * br.width() );
365       if ( arrowWidth <= 0 )
366         arrowWidth = height; // should not happen
367       arrowPixmap = QPixmap( arrowWidth, height );
368       arrowPixmap.fill( Qt::transparent );
369       QPainter painter( &arrowPixmap );
370       painter.setRenderHint( QPainter::Antialiasing );
371 
372       pic.render( &painter );
373       painter.end();
374     }
375   }
376 #endif
377 
378   QString plusPath = iconsPath + "grass_plus.svg";
379   QPixmap plusPixmap;
380   iconsfi.setFile( plusPath );
381 #if 0
382   if ( iconsfi.exists() && plusPixmap.load( plusPath, "PNG" ) )
383   {
384     double scale = 1. * height / plusPixmap.height();
385     plusWidth = ( int )( scale * plusPixmap.width() );
386 
387     QImage img = plusPixmap.toImage();
388     img = img.scaled( plusWidth, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
389     plusPixmap = QPixmap::fromImage( img );
390   }
391 #endif
392   if ( iconsfi.exists() )
393   {
394     QSvgRenderer pic;
395     if ( pic.load( plusPath ) )
396     {
397       QRect br( QPoint( 0, 0 ), pic.defaultSize() );
398 
399       double scale = 1. * height / br.height();
400 
401       plusWidth = ( int )( scale * br.width() );
402       if ( plusWidth <= 0 )
403         plusWidth = height; // should not happen
404       plusPixmap = QPixmap( plusWidth, height );
405       plusPixmap.fill( Qt::transparent );
406       QPainter painter( &plusPixmap );
407       painter.setRenderHint( QPainter::Antialiasing );
408 
409       pic.render( &painter );
410       painter.end();
411     }
412   }
413   int buffer = height / 3; // buffer around a sign
414   if ( pixmaps.size() > 1 )
415     width += arrowWidth + 2 * buffer; // ->
416   if ( pixmaps.size() > 2 )
417     width += plusWidth + 2 * buffer; // +
418 
419   QPixmap pixmap( width, height );
420   pixmap.fill( Qt::transparent );
421   //pixmap.fill( QColor( 255, 255, 255 ) );
422   QPainter painter( &pixmap );
423 
424   //QColor color( 255, 255, 255 );
425   //painter.setBrush( QBrush( color ) );
426 
427   painter.setRenderHint( QPainter::Antialiasing );
428 
429   int pos = 0;
430   for ( int i = 0; i < pixmaps.size(); i++ )
431   {
432     if ( i == 1 && pixmaps.size() == 3 )   // +
433     {
434       pos += buffer;
435       painter.drawPixmap( pos, 0, plusPixmap );
436       pos += buffer + plusWidth;
437     }
438     if ( ( i == 1 && pixmaps.size() == 2 ) || ( i == 2 && pixmaps.size() == 3 ) ) // ->
439     {
440       pos += buffer;
441       painter.drawPixmap( pos, 0, arrowPixmap );
442       pos += buffer + arrowWidth;
443     }
444     painter.drawPixmap( pos, 0, pixmaps[i] );
445     pos += pixmaps[i].width();
446   }
447   painter.end();
448 
449   return pixmap;
450 }
451 
run()452 void QgsGrassModule::run()
453 {
454   QgsDebugMsg( "called." );
455 
456   if ( mProcess.state() == QProcess::Running )
457   {
458     mProcess.kill();
459     mRunButton->setText( tr( "Run" ) );
460   }
461   else
462   {
463     //QString command;
464     QStringList arguments;
465 
466     //mProcess.clearArguments();
467     //mProcess.addArgument( mXName );
468     //command = mXName;
469 
470     // Check if options are ready
471     QStringList readyErrors = mOptions->ready();
472     if ( readyErrors.size() > 0 )
473     {
474       QString err;
475       for ( int i = 0; i < readyErrors.size(); i++ )
476       {
477         err.append( readyErrors.at( i ) + "<br>" );
478       }
479       QMessageBox::warning( nullptr, tr( "Warning" ), err );
480       return;
481     }
482 
483     // Check/set region
484     struct Cell_head tempWindow;
485     bool resetRegion = false;
486     QgsCoordinateReferenceSystem crs;
487     if ( mOptions->requestsRegion() ) // direct always
488     {
489       if ( !mOptions->inputRegion( &tempWindow, crs, false ) )
490       {
491         QMessageBox::warning( nullptr, tr( "Warning" ), tr( "Cannot get input region" ) );
492         return;
493       }
494       resetRegion = true;
495     }
496     else if ( mOptions->usesRegion() )
497     {
498       QStringList outsideRegion = mOptions->checkRegion();
499       if ( outsideRegion.size() > 0 )
500       {
501         QMessageBox questionBox( QMessageBox::Question, tr( "Warning" ),
502                                  tr( "Input %1 outside current region!" ).arg( outsideRegion.join( QLatin1Char( ',' ) ) ),
503                                  QMessageBox::Ok | QMessageBox::Cancel );
504         QPushButton *resetButton = nullptr;
505         if ( QgsGrass::versionMajor() > 6 || ( QgsGrass::versionMajor() == 6 && QgsGrass::versionMinor() >= 1 ) )
506         {
507           resetButton = questionBox.addButton( tr( "Use Input Region" ), QMessageBox::DestructiveRole );
508         }
509         questionBox.exec();
510         QAbstractButton *clicked = questionBox.clickedButton();
511         if ( clicked == questionBox.button( QMessageBox::Cancel ) )
512           return;
513         if ( clicked == resetButton )
514           resetRegion = true;
515 
516         if ( resetRegion )
517         {
518           if ( !mOptions->inputRegion( &tempWindow, crs, true ) )
519           {
520             QMessageBox::warning( nullptr, tr( "Warning" ), tr( "Cannot get input region" ) );
521             return;
522           }
523         }
524       }
525     }
526 
527     // In direct mode user is warned by select file dialog
528     if ( !mDirect )
529     {
530       // Check if output exists
531       QStringList outputExists = mOptions->checkOutput();
532       if ( outputExists.size() > 0 )
533       {
534         QMessageBox::StandardButton ret = QMessageBox::question( nullptr, QStringLiteral( "Warning" ),
535                                           tr( "Output %1 exists! Overwrite?" ).arg( outputExists.join( QLatin1Char( ',' ) ) ),
536                                           QMessageBox::Ok | QMessageBox::Cancel );
537 
538         if ( ret == QMessageBox::Cancel )
539           return;
540 
541         arguments.append( QStringLiteral( "--o" ) );
542       }
543     }
544 
545     // Remember output maps
546     mOutputVector = mOptions->output( QgsGrassModuleOption::Vector );
547     QgsDebugMsg( QString( "mOutputVector.size() = %1" ).arg( mOutputVector.size() ) );
548     mOutputRaster = mOptions->output( QgsGrassModuleOption::Raster );
549     QgsDebugMsg( QString( "mOutputRaster.size() = %1" ).arg( mOutputRaster.size() ) );
550     mSuccess = false;
551     mViewButton->setEnabled( false );
552 
553     QStringList list = mOptions->arguments();
554     list << arguments;
555 
556     QStringList argumentsHtml;
557     for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
558     {
559       QgsDebugMsg( "option: " + ( *it ) );
560       //command.append ( " " + *it );
561       arguments.append( *it );
562       //mProcess.addArgument( *it );
563 
564       // Quote options with special characters so that user
565       // can copy-paste-run the command
566       if ( it->contains( QRegExp( "[ <>\\$|;&]" ) ) )
567       {
568         argumentsHtml.append( "\"" + *it + "\"" );
569       }
570       else
571       {
572         argumentsHtml.append( *it );
573       }
574     }
575 
576     /* WARNING - TODO: there was a bug in GRASS 6.0.0 / 6.1.CVS (< 2005-04-29):
577      * db_start_driver set GISRC_MODE_MEMORY eviroment variable to 1 if
578      * G_get_gisrc_mode() == G_GISRC_MODE_MEMORY but the variable wasn't unset
579      * if  G_get_gisrc_mode() == G_GISRC_MODE_FILE. Because QGIS GRASS provider starts drivers in
580      * G_GISRC_MODE_MEMORY mode, the variable remains set in variable when a module is run
581      * -> unset GISRC_MODE_MEMORY. Remove later once 6.1.x / 6.0.1 is widespread.
582     */
583     putenv( ( char * ) "GISRC_MODE_MEMORY" ); // unset
584 
585     mOutputTextBrowser->clear();
586 
587     QProcessEnvironment environment = processEnvironment( mDirect );
588     environment.insert( QStringLiteral( "GRASS_HTML_BROWSER" ), QgsGrassUtils::htmlBrowserPath() );
589 
590     // Warning: it is not useful to write requested region to WIND file and
591     //          reset then to original because it is reset before
592     //          the region is read by a module even if waitForStarted() is used
593     //          -> necessary to pass region as environment variable
594     //             but the feature is available in GRASS 6.1 only since 23.3.2006
595     if ( resetRegion )
596     {
597       QString reg = QgsGrass::regionString( &tempWindow );
598       QgsDebugMsg( "reg: " + reg );
599       environment.insert( QStringLiteral( "GRASS_REGION" ), reg );
600     }
601 
602     if ( mDirect )
603     {
604       QStringList variables;
605       setDirectLibraryPath( environment );
606 #ifdef Q_OS_WIN
607       variables << "PATH";
608 #elif defined(Q_OS_MAC)
609       variables << "DYLD_LIBRARY_PATH";
610 #else
611       variables << QStringLiteral( "LD_LIBRARY_PATH" );
612 #endif
613       environment.insert( QStringLiteral( "QGIS_PREFIX_PATH" ), QgsApplication::prefixPath() );
614       if ( crs.isValid() ) // it should always be valid
615       {
616         environment.insert( QStringLiteral( "QGIS_GRASS_CRS" ), crs.toProj() );
617       }
618       // Suppress debug output
619       environment.insert( QStringLiteral( "QGIS_DEBUG" ), QStringLiteral( "-1" ) );
620 
621       // Print some important variables
622       variables << QStringLiteral( "QGIS_PREFIX_PATH" ) << QStringLiteral( "QGIS_GRASS_CRS" ) << QStringLiteral( "GRASS_REGION" );
623       Q_FOREACH ( const QString &v, variables )
624       {
625         mOutputTextBrowser->append( v + "=" + environment.value( v ) + "<BR>" );
626       }
627     }
628 
629     QString commandHtml = mXName + " " + argumentsHtml.join( QLatin1Char( ' ' ) );
630 
631     QgsDebugMsg( "command: " + commandHtml );
632     commandHtml.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) );
633     commandHtml.replace( QLatin1String( "<" ), QLatin1String( "&lt;" ) );
634     commandHtml.replace( QLatin1String( ">" ), QLatin1String( "&gt;" ) );
635     mOutputTextBrowser->append( "<B>" +  commandHtml + "</B>" );
636 
637     // I was not able to get scripts working on Windows
638     // via QProcess and sh.exe (MinGW). g.parser runs wellQProcessEnvironment::systemE
639     // and it sets parameters correctly as environment variables
640     // but it fails (without error) to re-run the script with
641     // execlp(). And I could not figure out why it fails.
642     // Because of this problem we simulate here what g.parser
643     // normally does and that way we can avoid it.
644 
645     QStringList execArguments = QgsGrassModule::execArguments( mXName );
646 
647     if ( execArguments.size() == 0 )
648     {
649       QMessageBox::warning( nullptr, tr( "Warning" ), tr( "Cannot find module %1" ).arg( mXName ) );
650       return;
651     }
652 
653 #ifdef Q_OS_WIN
654     // we already know it exists from execArguments()
655     QString exe = QgsGrass::findModule( mXName );
656     QFileInfo fi( exe );
657     if ( !fi.isExecutable() )
658     {
659       QStringList usedFlagNames;
660 
661       // Set environment variables
662       for ( int i = 0; i < arguments.size(); i++ )
663       {
664         QString arg = arguments.at( i );
665         //QString env;
666         if ( arg.at( 0 ) == '-' ) //flag
667         {
668           //env = "GIS_FLAG_" + QString( arg.at( 1 ).toUpper() ) + "=1";
669           environment.insert( "GIS_FLAG_" + QString( arg.at( 1 ).toUpper() ), "1" );
670           usedFlagNames.append( arg.at( 1 ) );
671         }
672         else // option
673         {
674           QStringList opt = arg.split( '=' );
675           //env = "GIS_OPT_" + opt.takeFirst().toUpper();
676           //env += "=" + opt.join( "=" ); // rejoin rest
677           environment.insert( "GIS_OPT_" + opt.takeFirst().toUpper(), opt.join( "=" ) );
678         }
679         //environment.append( env );
680       }
681 
682       // Set remaining flags
683       QStringList allFlagNames = mOptions->flagNames();
684       for ( int i = 0; i < allFlagNames.size(); i++ )
685       {
686         bool used = false;
687         for ( int j = 0; j < usedFlagNames.size(); j++ )
688         {
689           if ( usedFlagNames.at( j ) == allFlagNames.at( i ) )
690           {
691             used = true;
692             break;
693           }
694         }
695         if ( used )
696           continue;
697         //QString env = "GIS_FLAG_"
698         //              + QString( allFlagNames.at( i ).toUpper() )
699         //              + "=0";
700         //QgsDebugMsg( "set: " + env );
701         //environment.append( env );
702         environment.insert( "GIS_FLAG_" + QString( allFlagNames.at( i ).toUpper() ), "0" );
703       }
704 
705       arguments.clear();
706       arguments.append( "@ARGS_PARSED@" );
707     }
708 #endif
709 
710     QString cmd = execArguments.takeFirst();
711     execArguments += arguments;
712 
713     // Freeze output vector on Windows
714     mOptions->freezeOutput();
715 
716     mProcess.setProcessEnvironment( environment );
717     mProcess.start( cmd, execArguments );
718     emit moduleStarted();
719 
720     mProcess.waitForStarted();
721     if ( mProcess.state() != QProcess::Running )
722     {
723       QMessageBox::warning( nullptr, tr( "Warning" ), tr( "Cannot start module: %1" ).arg( mProcess.errorString() ) );
724       return;
725     }
726 
727     mTabWidget->setCurrentIndex( 1 );
728     mRunButton->setText( tr( "Stop" ) );
729   }
730 }
731 
finished(int exitCode,QProcess::ExitStatus exitStatus)732 void QgsGrassModule::finished( int exitCode, QProcess::ExitStatus exitStatus )
733 {
734   QgsDebugMsg( "called." );
735 
736   QgsDebugMsg( QString( "exitCode = %1" ).arg( exitCode ) );
737   if ( exitStatus == QProcess::NormalExit )
738   {
739     if ( exitCode == 0 )
740     {
741       mOutputTextBrowser->append( tr( "<B>Successfully finished</B>" ) );
742       setProgress( 100, true );
743       mSuccess = true;
744       mViewButton->setEnabled( !mOutputVector.isEmpty() || !mOutputRaster.isEmpty() );
745       mOptions->freezeOutput( false );
746       mCanvas->refresh();
747     }
748     else
749     {
750       mOutputTextBrowser->append( tr( "<B>Finished with error</B>" ) );
751     }
752   }
753   else
754   {
755     mOutputTextBrowser->append( tr( "<B>Module crashed or killed</B>" ) );
756   }
757 
758   emit moduleFinished();
759   mRunButton->setText( tr( "Run" ) );
760 }
761 
readStdout()762 void QgsGrassModule::readStdout()
763 {
764   QgsDebugMsg( "called." );
765 
766   QString line;
767   QRegExp rxpercent( "GRASS_INFO_PERCENT: (\\d+)" );
768 
769   mProcess.setReadChannel( QProcess::StandardOutput );
770   while ( mProcess.canReadLine() )
771   {
772     QByteArray ba = mProcess.readLine();
773     line = QString::fromLocal8Bit( ba ).replace( '\n', QString() );
774 
775     // GRASS_INFO_PERCENT is caught here only because of bugs in GRASS,
776     // normally it should be printed to stderr
777     if ( rxpercent.indexIn( line ) != -1 )
778     {
779       int progress = rxpercent.cap( 1 ).toInt();
780       setProgress( progress );
781     }
782     else
783     {
784       mOutputTextBrowser->append( line );
785     }
786   }
787 }
788 
readStderr()789 void QgsGrassModule::readStderr()
790 {
791   QgsDebugMsg( "called." );
792 
793   QString line;
794 
795   mProcess.setReadChannel( QProcess::StandardError );
796   while ( mProcess.canReadLine() )
797   {
798     QByteArray ba = mProcess.readLine();
799     line = QString::fromLocal8Bit( ba ).replace( '\n', QString() );
800 
801     QString text, html;
802     int percent;
803     QgsGrass::ModuleOutput type = QgsGrass::parseModuleOutput( line, text, html, percent );
804     if ( type == QgsGrass::OutputPercent )
805     {
806       setProgress( percent );
807     }
808     else if ( type == QgsGrass::OutputMessage || type == QgsGrass::OutputWarning || type == QgsGrass::OutputError )
809     {
810       mOutputTextBrowser->append( html );
811     }
812   }
813 }
814 
setProgress(int percent,bool force)815 void QgsGrassModule::setProgress( int percent, bool force )
816 {
817   int max = 100;
818   // Do not set 100% until module finished, see #3131
819   if ( percent >= 100 && !force )
820   {
821     max = 0; // busy indicator
822     percent = 0;
823   }
824   mProgressBar->setMaximum( max );
825   mProgressBar->setValue( percent );
826 }
827 
close()828 void QgsGrassModule::close()
829 {
830   delete this;
831 }
832 
viewOutput()833 void QgsGrassModule::viewOutput()
834 {
835   QgsDebugMsg( "called." );
836 
837   if ( !mSuccess )
838     return;
839 
840   for ( int i = 0; i < mOutputVector.size(); i++ )
841   {
842     QString map = mOutputVector.at( i );
843 
844     if ( mDirect )
845     {
846       // TODO, maybe
847     }
848     else
849     {
850       QStringList layers;
851       try
852       {
853         layers = QgsGrass::vectorLayers(
854                    QgsGrass::getDefaultGisdbase(),
855                    QgsGrass::getDefaultLocation(),
856                    QgsGrass::getDefaultMapset(), map );
857       }
858       catch ( QgsGrass::Exception &e )
859       {
860         QgsDebugMsg( e.what() );
861         continue;
862       }
863 
864       // check whether there are 1_* layers
865       // if so, 0_* layers won't be added
866       bool onlyLayer1 = false;
867       for ( int j = 0; j < layers.count(); j++ )
868       {
869         if ( layers[j].at( 0 ) == '1' )
870         {
871           onlyLayer1 = true;
872           break;
873         }
874       }
875 
876       // TODO common method for add all layers
877       for ( int j = 0; j < layers.count(); j++ )
878       {
879         QString uri = QgsGrass::getDefaultGisdbase() + "/"
880                       + QgsGrass::getDefaultLocation() + "/"
881                       + QgsGrass::getDefaultMapset() + "/"
882                       + map + "/" + layers[j];
883 
884         // skip 0_* layers
885         if ( onlyLayer1 && layers[j].at( 0 ) != '1' )
886           continue;
887 
888         QString name = QgsGrassUtils::vectorLayerName(
889                          map, layers[j], 1 );
890 
891         mIface->addVectorLayer( uri, name, QStringLiteral( "grass" ) );
892       }
893     }
894   }
895 
896   for ( int i = 0; i < mOutputRaster.size(); i++ )
897   {
898     QString map = mOutputRaster.at( i );
899 
900     if ( mDirect )
901     {
902       QString baseName = QFileInfo( map ).baseName();
903       mIface->addRasterLayer( map, baseName, QStringLiteral( "gdal" ) );
904     }
905     else
906     {
907       QString uri = QgsGrass::getDefaultGisdbase() + "/"
908                     + QgsGrass::getDefaultLocation() + "/"
909                     + QgsGrass::getDefaultMapset()
910                     + "/cellhd/" + map;
911 
912       mIface->addRasterLayer( uri, map, QStringLiteral( "grassraster" ) );
913     }
914   }
915 }
916 
qgisIface()917 QgisInterface *QgsGrassModule::qgisIface()
918 {
919   return mIface;
920 }
921 
~QgsGrassModule()922 QgsGrassModule::~QgsGrassModule()
923 {
924   QgsDebugMsg( "called." );
925   if ( mProcess.state() == QProcess::Running )
926   {
927     mProcess.kill();
928   }
929 }
930 
translate(QString msg)931 QString QgsGrassModule::translate( QString msg )
932 {
933   return QString::fromUtf8( G_gettext( "grassmods", msg.trimmed().toUtf8() ) );
934 }
935 
libraryPathVariable()936 QString QgsGrassModule::libraryPathVariable()
937 {
938 #ifdef Q_OS_WIN
939   return "PATH";
940 #elif defined(Q_OS_MAC)
941   return "DYLD_LIBRARY_PATH";
942 #else
943   return QStringLiteral( "LD_LIBRARY_PATH" );
944 #endif
945 }
946 
setDirectLibraryPath(QProcessEnvironment & environment)947 void QgsGrassModule::setDirectLibraryPath( QProcessEnvironment &environment )
948 {
949   QString pathVariable = libraryPathVariable();
950   QString separator;
951 #ifdef Q_OS_WIN
952   separator = ";";
953 #elif defined(Q_OS_MAC)
954   separator = ":";
955 #else
956   separator = QStringLiteral( ":" );
957 #endif
958   QString lp = environment.value( pathVariable );
959   lp = QgsApplication::pluginPath() + separator + lp;
960   environment.insert( pathVariable, lp );
961   QgsDebugMsg( pathVariable + "=" + lp );
962 }
963 
964