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( "&" ) );
633 commandHtml.replace( QLatin1String( "<" ), QLatin1String( "<" ) );
634 commandHtml.replace( QLatin1String( ">" ), QLatin1String( ">" ) );
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