Rev 1221 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/********************************************************************************************
** iLand - an individual based forest landscape and disturbance model
** http://iland.boku.ac.at
** Copyright (C) 2009- Werner Rammer, Rupert Seidl
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
********************************************************************************************/
#include <QtCore>
#include <QtWidgets>
#include <QtXml>
#include <QQuickView>
#include <QQmlEngine>
#include <QQmlContext>
#include <signal.h>
#include "global.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "aboutdialog.h"
#include "model.h"
#include "standloader.h"
#include "stampcontainer.h"
#include "resourceunit.h"
#include "speciesset.h"
#include "tree.h"
#include "species.h"
#include "seeddispersal.h"
#include "saplings.h"
#include "climate.h"
#include "exception.h"
#include "helper.h"
#include "colors.h"
#include "debugtimer.h"
#include "statdata.h"
#include "paintarea.h"
#include "expression.h"
#include "expressionwrapper.h"
#include "management.h"
#include "outputmanager.h"
#include "tests.h"
#include "mapgrid.h"
#include "layeredgrid.h"
#include "dem.h"
#include "forestmanagementengine.h" // ABE
// global settings
static QDomDocument xmldoc;
static QDomNode xmlparams;
/** @class MainWindow
@ingroup GUI
The main window of the iLand viewer.
*/
double distance(const QPointF &a, const QPointF &b)
{
return sqrt( (a.x()-b.x())*(a.x()-b.x()) + (a.y()-b.y())*(a.y()-b.y()) );
}
double nrandom(const float& p1, const float& p2)
{
return p1 + (p2-p1)*(rand()/float(RAND_MAX));
}
static bool showDebugMessages=true;
static QStringList bufferedMessages;
static bool doBufferMessages = false;
static bool doLogToWindow = false;
void logToWindow(bool mode)
{
doLogToWindow = mode;
}
class LogToWindow
{
public:
LogToWindow() { logToWindow(true);}
~LogToWindow() { logToWindow(false);}
};
static QMutex qdebug_mutex;
void dumpMessages();
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
Q_UNUSED(context);
QMutexLocker m(&qdebug_mutex);
//QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
if (showDebugMessages) {
if (qstrcmp(context.category, "default")!=0)
bufferedMessages.append(QString("%1: %2").arg(context.category).arg(msg));
else
bufferedMessages.append(QString(msg));
}
break;
case QtWarningMsg:
//case QtInfoMsg:
//MainWindow::logSpace()->appendPlainText(QString("WARNING: %1").arg(msg));
//MainWindow::logSpace()->ensureCursorVisible();
bufferedMessages.append(msg);
break;
case QtCriticalMsg: {
QByteArray localMsg = msg.toLocal8Bit();
fprintf(stderr, "Critical: %s\n", localMsg.constData());
break; }
case QtFatalMsg: {
QByteArray localMsg = msg.toLocal8Bit();
fprintf(stderr, "Fatal: %s\n", localMsg.constData());
bufferedMessages.append(msg);
QString file_name = GlobalSettings::instance()->path("fatallog.txt","log");
Helper::msg(QString("Fatal message encountered:\n%1\nFatal-Log-File: %2").arg(msg, file_name));
dumpMessages();
Helper::saveToTextFile(file_name, MainWindow::logSpace()->toPlainText() + bufferedMessages.join("\n"));
}
}
if (!doBufferMessages || bufferedMessages.count()>5000)
dumpMessages();
}
static QMutex dump_message_mutex;
void dumpMessages()
{
QMutexLocker m(&dump_message_mutex); // serialize access
// 2011-03-08: encountered "strange" crashes
// when a warning within Qt lead to a infinite loop/deadlock (also caused by mutex locking)
// now we prevent this by installing temporarily a 0-handler
qInstallMessageHandler(0);
if (MainWindow::logStream() && !doLogToWindow) {
foreach(const QString &s, bufferedMessages)
*MainWindow::logStream() << s << endl;
MainWindow::logStream()->flush();
} else {
foreach(const QString &s, bufferedMessages)
MainWindow::logSpace()->appendPlainText(s);
// it does *not* work to just MainWindow::logSpace()->textCursor().movePosition()!
// you have to "setTextCursor()".
QTextCursor cursor = MainWindow::logSpace()->textCursor();
cursor.movePosition(QTextCursor::End);
MainWindow::logSpace()->setTextCursor(cursor);
MainWindow::logSpace()->ensureCursorVisible();
}
bufferedMessages.clear();
qInstallMessageHandler(myMessageOutput);
}
// handle signal...
// source: http://cplusplus.com/forum/unices/13455/
void handle_signal( int signo ) {
Helper::msg(QString("Received Signal:\n%1").arg(signo));
qDebug() << "*** Received signal "<< signo << "****";
dumpMessages();
}
void MainWindow::bufferedLog(bool bufferLog)
{
doBufferMessages = bufferLog;
if (bufferLog==false)
dumpMessages();
}
void MainWindow::setupFileLogging(const bool do_start)
{
if (mLogStream) {
if (mLogStream->device())
delete mLogStream->device();
delete mLogStream;
mLogStream = NULL;
}
if (!do_start)
return;
if (GlobalSettings::instance()->settings().value("system.logging.logTarget", "console") == "file") {
QString fname = GlobalSettings::instance()->settings().value("system.logging.logFile", "logfile.txt");
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
fname.replace("$date$", timestamp);
fname = GlobalSettings::instance()->path(fname, "log");
QFile *file = new QFile(fname);
if (!file->open(QIODevice::WriteOnly)) {
qDebug() << "cannot open logfile" << fname;
} else {
qDebug() << "Log output is redirected to logfile" << fname;
mLogStream = new QTextStream(file);
}
}
}
QPlainTextEdit *MainWindow::mLogSpace=NULL;
QTextStream *MainWindow::mLogStream=NULL;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindowClass)
{
ui->setupUi(this);
connect( ui->PaintWidget, SIGNAL(needsPainting(QPainter&)),
this, SLOT(repaintArea(QPainter&)) );
connect (ui->PaintWidget, SIGNAL(mouseClick(QPoint)),
this, SLOT(mouseClick(const QPoint&)));
connect(ui->PaintWidget, SIGNAL(mouseDrag(QPoint,QPoint,Qt::MouseButton)),
this, SLOT(mouseDrag(const QPoint&, const QPoint &, const Qt::MouseButton)));
connect(ui->PaintWidget, SIGNAL(mouseMove(QPoint)),
this, SLOT(mouseMove(const QPoint&)));
connect(ui->PaintWidget, SIGNAL(mouseWheel(QPoint, int)),
this, SLOT(mouseWheel(const QPoint&, int)));
// dock windows
ui->menuView->addAction( ui->dockEditor->toggleViewAction() );
ui->menuView->addAction( ui->dockLogviewer->toggleViewAction() );
ui->menuView->addAction( ui->dockWidget->toggleViewAction() );
ui->menuView->addAction( ui->dockModelDrill->toggleViewAction() );
ui->pbResults->setMenu(ui->menuOutput_menu);
mLogSpace = ui->logOutput;
mLogSpace->setMaximumBlockCount(1000000); // set a maximum for the in-GUI size of messages. // removed in Qt5 (because it works ;) )
qInstallMessageHandler(myMessageOutput);
// install signal handler
signal( SIGSEGV, handle_signal );
readSettings();
// create global scripting context
GlobalSettings::instance()->resetScriptEngine();
// load xml file: use either a command-line argument (if present), or load the content of a small text file....
QString argText = QApplication::arguments().last();
if (QApplication::arguments().count()>1 && !argText.isEmpty()) {
ui->initFileName->setText(argText);
} else {
QString lastXml = QSettings().value("project/lastxmlfile").toString();
//QString lastXml = Helper::loadTextFile( QCoreApplication::applicationDirPath()+ "/lastxmlfile.txt" );
if (!lastXml.isEmpty() && QFile::exists(lastXml))
ui->initFileName->setText(lastXml);
}
QString xmlFile = Helper::loadTextFile(ui->initFileName->text());
if (!xmlFile.isEmpty()) {
ui->iniEdit->setPlainText(xmlFile);
QString errMsg;
int errLine, errCol;
if (!xmldoc.setContent(xmlFile, &errMsg, &errLine, &errCol)) {
QMessageBox::information(this, "title text", QString("Cannot set content of XML file %1. \nat line %2 col %3: %4 ")
.arg(ui->initFileName->text()).arg(errLine).arg(errCol).arg(errMsg));
//return;
}
}
on_actionEdit_XML_settings_triggered();
qDebug() << "threadcount: " << QThread::idealThreadCount();
// load window settings
QString fileName = QDir::current().filePath("gui.txt");
if (QFile::exists(fileName)) {
QByteArray state = Helper::loadFile(fileName);
restoreState(state);
}
checkModelState();
ui->statusBar->addPermanentWidget(ui->modelRunProgress);
ui->modelRunProgress->setValue(0);
mStatusLabel = new QLabel(this);
labelMessage("no model created.");
ui->statusBar->addWidget(mStatusLabel);
// remote control of model
connect(&mRemoteControl, SIGNAL(year(int)),this,SLOT(yearSimulated(int)));
connect(&mRemoteControl, SIGNAL(finished(QString)), this, SLOT(modelFinished(QString)));
connect(&mRemoteControl, SIGNAL(stateChanged()), this, SLOT(checkModelState()));
// log levels
ui->actionDebug->setProperty("logLevel", QVariant(0));
ui->actionInfo->setProperty("logLevel", QVariant(1));
ui->actionWarning->setProperty("logLevel", QVariant(2));
ui->actionError->setProperty("logLevel", QVariant(3));
// species filter
connect( ui->speciesFilterBox, SIGNAL(currentIndexChanged(int)), SLOT(repaint()));
connect( ui->paintGridBox, SIGNAL(currentIndexChanged(int)), SLOT(repaint()));
updatePaintGridList();
// model controller
mRemoteControl.setMainWindow( this );
mRemoteControl.connectSignals();
GlobalSettings::instance()->setModelController( &mRemoteControl );
// automatic run
if (QApplication::arguments().contains("run")) {
QTimer::singleShot(3000, this, SLOT(automaticRun()));
}
// to silence some warnings during startup - maybe not required (anymore):
qRegisterMetaType<QTextBlock>("QTextBlock");
qRegisterMetaType<QTextCursor>("QTextCursor");
ui->iniEdit->setVisible(false);
ui->editStack->setTabEnabled(3,false); // the "other" tab
// qml setup
QQuickView *view = new QQuickView();
mRuler = view;
QWidget *container = QWidget::createWindowContainer(view, this);
mRulerColors = new Colors();
view->engine()->rootContext()->setContextProperty("rulercolors", mRulerColors);
view->setResizeMode(QQuickView::SizeRootObjectToView);
ui->pbReloadQml->setVisible(false); // enable for debug...
//view->setSource(QUrl::fromLocalFile("E:/dev/iland_port_qt5_64bit/src/iland/qml/ruler.qml"));
view->setSource(QUrl("qrc:/qml/ruler.qml"));
//view->show();
ui->qmlRulerLayout->addWidget(container);
ui->qmlRulerLayout->addWidget(container);
// QDir d(":/qml");
// qDebug() << d.entryList();
}
MainWindow::~MainWindow()
{
mRemoteControl.destroy(); // delete model and free resources.
delete ui;
}
void MainWindow::batchLog(const QString s)
{
QFile outfile(QCoreApplication::applicationDirPath()+ "/batchlog.txt");
if (outfile.open(QIODevice::Append | QIODevice::Text)) {
QTextStream str(&outfile);
str << s << endl;
}
}
/// automatically start a simulation...
void MainWindow::automaticRun()
{
// start a simulation
setWindowTitle("iLand viewer --- batch mode");
batchLog("*** iland batch mode ***");
batchLog(QString("%1 Loading project %2").arg(QDateTime::currentDateTime().toString(Qt::ISODate),
ui->initFileName->text()));
batchLog(QString("%1 Loading the model...").arg(QDateTime::currentDateTime().toString(Qt::ISODate)));
setupModel();
int count = QCoreApplication::arguments()[QCoreApplication::arguments().count()-2].toInt();
if (count==0) {
qDebug() << "invalid number of years....";
return;
}
ui->modelRunProgress->setMaximum(count-1);
batchLog(QString("%1 Running the model (%2 years)...").arg(QDateTime::currentDateTime().toString(Qt::ISODate)).arg(count));
// note the "+1": this is similar to the normal way of starting
// "1" means in Globals.year that we are in the 1st year.
// the simulation stops when reaching the count+1 year.
mRemoteControl.run(count + 1);
// see the finsished() slot
}
// simply command an update of the painting area
void MainWindow::repaint()
{
ui->PaintWidget->update();
QCoreApplication::processEvents();
}
// control GUI actions
void MainWindow::checkModelState()
{
ui->actionModelCreate->setEnabled(mRemoteControl.canCreate()&& !mRemoteControl.isRunning());
ui->actionModelDestroy->setEnabled(mRemoteControl.canDestroy() && !mRemoteControl.isRunning());
ui->actionModelRun->setEnabled(mRemoteControl.canRun() && !mRemoteControl.isPaused() && !mRemoteControl.isRunning());
ui->actionRun_one_year->setEnabled(mRemoteControl.canRun() && !mRemoteControl.isPaused()&& !mRemoteControl.isRunning());
ui->actionReload->setEnabled(mRemoteControl.canDestroy() && !mRemoteControl.isRunning());
ui->actionStop->setEnabled(mRemoteControl.isRunning());
ui->actionPause->setEnabled(mRemoteControl.isRunning());
ui->actionPause->setText(mRemoteControl.isPaused()?"Continue":"Pause");
dumpMessages();
}
void MainWindow::readwriteCycle()
{
if (!mRemoteControl.canRun())
return;
Model *model = mRemoteControl.model();
model->onlyApplyLightPattern();
}
QString MainWindow::dumpTreelist()
{
if (!mRemoteControl.isRunning())
return "";
Model *model = mRemoteControl.model();
AllTreeIterator at(model);
DebugList treelist;
QString line;
QStringList result;
result << "id;species;dbh;height;x;y;RU#;LRI;mWoody;mRoot;mFoliage;LA";
while (Tree *tree = at.next()) {
treelist.clear();
tree->dumpList(treelist);
line = "";
foreach(QVariant value, treelist)
line+=value.toString() + ";";
result << line;
}
QString resStr = result.join("\n");
return resStr;
}
void MainWindow::updatePaintGridList()
{
ui->paintGridBox->clear();
ui->paintGridBox->addItem("<none>", "");
QMap<QString, PaintObject>::const_iterator i = mPaintList.begin();
while (i!=mPaintList.constEnd()) {
ui->paintGridBox->addItem(i.key(),i.key());
++i;
}
}
void MainWindow::addLayers(const LayeredGridBase *layer, const QString &name)
{
const QVector<LayeredGridBase::LayerElement> &names = const_cast<LayeredGridBase*>(layer)->names();
int layer_id = 0;
QString current_layer = mPaintNext.name;
foreach (const LayeredGridBase::LayerElement &layername, names) {
QString comb_name = QString("%1 - %2").arg(name, layername.name);
PaintObject po;
po.what = PaintObject::PaintLayers;
po.view_type = layername.view_type;
po.layered = layer;
po.layer_id = layer_id++;
po.name = layername.name;
po.auto_range = true;
mPaintList[comb_name] = po;
if (current_layer == po.name)
mPaintNext.what = PaintObject::PaintHeightGrid;
}
updatePaintGridList();
}
void MainWindow::removeLayers(const LayeredGridBase *layer)
{
QMap<QString, PaintObject>::iterator it=mPaintList.begin();
while(it!=mPaintList.end())
if (it->layered == layer)
it = mPaintList.erase(it);
else
++it;
if (mPaintNext.layered == layer)
mPaintNext.what = PaintObject::PaintHeightGrid;
}
void MainWindow::paintGrid(MapGrid *map_grid, const QString &name,
const GridViewType view_type,
double min_val, double max_val)
{
if (map_grid==0 && !name.isEmpty()) {
// remove the grid from the list
mPaintList.remove(name);
updatePaintGridList();
return;
}
mPaintNext.what=PaintObject::PaintMapGrid;
mPaintNext.min_value=min_val; mPaintNext.max_value=max_val;
mPaintNext.map_grid = map_grid; mPaintNext.view_type = view_type;
if (!name.isEmpty()) {
updatePaintGridList();
mPaintList[name] = mPaintNext;
}
ui->visOtherGrid->setChecked(true);
repaint();
}
void MainWindow::paintGrid(const FloatGrid *grid, const QString &name,
const GridViewType view_type,
double min_val, double max_val)
{
if (grid==0 && !name.isEmpty()) {
// remove the grid from the list
mPaintList.remove(name);
updatePaintGridList();
return;
}
mPaintNext.what=PaintObject::PaintFloatGrid;
mPaintNext.min_value=min_val;
mPaintNext.max_value=max_val;
mPaintNext.float_grid = grid;
mPaintNext.view_type = view_type;
mPaintNext.name = name;
if (!name.isEmpty()) {
mPaintList[name] = mPaintNext;
updatePaintGridList();
}
ui->visOtherGrid->setChecked(true);
repaint();
}
void MainWindow::paintFON(QPainter &painter, QRect rect)
{
DebugTimer drawtimer("painting");
drawtimer.setSilent();
if (!mRemoteControl.canRun())
return;
Model *model = mRemoteControl.model();
FloatGrid *grid = model->grid();
HeightGrid *domGrid = model->heightGrid();
// do the actual painting
if (!grid)
return;
bool auto_scale_color = ui->visAutoScale->isChecked();
bool show_fon = ui->visFon->isChecked();
bool show_dom = ui->visDomGrid->isChecked();
bool show_trees = ui->visImpact->isChecked();
bool species_color = ui->visSpeciesColor->isChecked();
bool show_ru = ui->visResourceUnits->isChecked();
bool show_regeneration = ui->visRegeneration->isChecked();
bool show_seedmaps = ui->visSeeds->isChecked();
bool other_grid = ui->visOtherGrid->isChecked();
if (other_grid) {
// return; // TODO TEST
if (ui->paintGridBox->currentIndex()>-1) {
QString name = ui->paintGridBox->itemData(ui->paintGridBox->currentIndex()).toString();
if (!name.isEmpty())
mPaintNext = mPaintList[name];
}
if (mPaintNext.what != PaintObject::PaintNothing) {
if (mPaintNext.what == PaintObject::PaintMapGrid) {
mRulerColors->setCaption(mPaintNext.name);
paintMapGrid(painter, mPaintNext.map_grid, 0, mPaintNext.view_type, mPaintNext.min_value, mPaintNext.max_value);
}
if (mPaintNext.what == PaintObject::PaintFloatGrid) {
mRulerColors->setCaption(mPaintNext.name);
paintMapGrid(painter, 0, mPaintNext.float_grid, mPaintNext.view_type, mPaintNext.min_value, mPaintNext.max_value);
}
if (mPaintNext.what == PaintObject::PaintLayers)
paintGrid(painter, mPaintNext);
return;
}
}
// clear background
painter.fillRect(ui->PaintWidget->rect(), Qt::white);
// draw rectangle around the grid
QRectF r = grid->metricRect();
QRect rs = vp.toScreen(r);
painter.setPen(Qt::darkGray);
painter.drawRect(rs);
//qDebug() << rs;
// what to paint??
float maxval=1.f; // default maximum
float minval=0.f;
if (!auto_scale_color)
maxval =grid->max();
if (maxval==0.)
return;
QString species;
if (ui->speciesFilterBox->currentIndex()>-1)
species = ui->speciesFilterBox->itemData(ui->speciesFilterBox->currentIndex()).toString();
int ix,iy;
QColor fill_color;
float value;
if (show_fon ) {
// start from each pixel and query value in grid for the pixel
mRulerColors->setCaption("Light Influence Field", "value of the LIF at 2m resolution.");
mRulerColors->setPalette(GridViewRainbowReverse,0., maxval); // ruler
if (!mRulerColors->autoScale()) {
maxval = static_cast<float>( mRulerColors->maxValue());
minval = static_cast<float>( mRulerColors->minValue() );
}
int x,y;
int sizex = rect.width();
int sizey = rect.height();
QPointF world;
QRgb col;
QImage &img = ui->PaintWidget->drawImage();
for (x=0;x<sizex;x++)
for (y=0;y<sizey;y++) {
world = vp.toWorld(QPoint(x,y));
if (grid->coordValid(world)) {
value = grid->valueAt(world);
col = Colors::colorFromValue(value, minval, maxval,true).rgb();
img.setPixel(x,y,col);
}
}
}
if (show_seedmaps) {
if (species.isEmpty()) {
qDebug() << "Please select a species!";
return;
}
int x,y;
mRulerColors->setCaption("Seed availability", QString("seed availability of species %1").arg(species));
mRulerColors->setPalette(GridViewRainbow,0., 1.); // ruler
int sizex = rect.width();
int sizey = rect.height();
QPointF world;
QRgb col;
QImage &img = ui->PaintWidget->drawImage();
const Grid<float> &grid = GlobalSettings::instance()->model()->speciesSet()->species(species)->seedDispersal()->seedMap();
QRgb gray_bg = QColor(100,100,100).rgb();
for (x=0;x<sizex;x++)
for (y=0;y<sizey;y++) {
world = vp.toWorld(QPoint(x,y));
if (grid.coordValid(world)) {
value = grid.constValueAt(world);
col = value>0.f ? Colors::colorFromValue(value, 0., 1., false).rgb() : gray_bg;
img.setPixel(x,y,col);
}
}
}
if (show_regeneration ) {
if (mRegenerationGrid.isEmpty())
mRegenerationGrid.setup(*model->grid()); // copy
if (!GlobalSettings::instance()->model()->saplings())
return;
static int last_year=0;
static QString last_species="";
static bool last_regen_mode=false;
if (last_year!=GlobalSettings::instance()->currentYear() || species!=last_species || ui->visRegenNew->isChecked()!=last_regen_mode) {
last_year=GlobalSettings::instance()->currentYear();
last_species=species;
bool draw_established = ui->visRegenNew->isChecked();
last_regen_mode = draw_established;
// fill grid...
DebugTimer t("create regeneration map...");
mRegenerationGrid.wipe(0.f);
if (species.isEmpty()) {
// hmax of all species
for (float *rg=mRegenerationGrid.begin();rg!=mRegenerationGrid.end(); ++rg) {
SaplingCell *sc=GlobalSettings::instance()->model()->saplings()->cell(mRegenerationGrid.indexOf(rg));
if (sc) {
if (draw_established)
*rg = sc->has_new_saplings() ? 1.f : 0.f;
else
*rg = sc->max_height();
}
}
} else {
// filter a specific species
int sidx = GlobalSettings::instance()->model()->speciesSet()->species(species)->index();
for (float *rg=mRegenerationGrid.begin(); rg!=mRegenerationGrid.end(); ++rg) {
SaplingCell *sc=GlobalSettings::instance()->model()->saplings()->cell(mRegenerationGrid.indexOf(rg));
if (sc) {
SaplingTree *st=sc->sapling(sidx);
if (st) {
if (draw_established)
*rg = st->is_occupied() && st->age<2 ? 1.f : 0.f;
else
*rg = st ? st->height : 0.f;
}
}
}
}
}
// start from each pixel and query value in grid for the pixel
int x,y;
mRulerColors->setCaption("Regeneration Layer", "max. tree height of regeneration layer (blue=0m, red=4m)");
mRulerColors->setPalette(GridViewRainbow,0., 4.); // ruler
int sizex = rect.width();
int sizey = rect.height();
QPointF world;
QRgb col;
QImage &img = ui->PaintWidget->drawImage();
for (x=0;x<sizex;x++)
for (y=0;y<sizey;y++) {
world = vp.toWorld(QPoint(x,y));
if (mRegenerationGrid.coordValid(world)) {
value = mRegenerationGrid.valueAt(world);
col = Colors::colorFromValue(value, 0., 4., false).rgb(); // 0..4m
img.setPixel(x,y,col);
}
}
}
if (show_dom) {
// paint the lower-res-grid;
float max_val = 50.f;
float min_val = 0.f;
if (auto_scale_color) {
max_val = 0.;
for (HeightGridValue *v = domGrid->begin(); v!=domGrid->end(); ++v)
max_val = qMax(max_val, v->height);
}
mRulerColors->setCaption("Dominant height (m)", "dominant tree height on 10m pixel.");
mRulerColors->setPalette(GridViewRainbow,0., max_val); // ruler
if (!mRulerColors->autoScale()) {
min_val = static_cast<float>( mRulerColors->minValue() );
max_val = static_cast<float>( mRulerColors->maxValue() );
}
for (iy=0;iy<domGrid->sizeY();iy++) {
for (ix=0;ix<domGrid->sizeX();ix++) {
QPoint p(ix,iy);
const HeightGridValue &hgv = domGrid->valueAtIndex(p);
if (hgv.isValid()) {
value = domGrid->valueAtIndex(p).height;
QRect r = vp.toScreen(domGrid->cellRect(p));
fill_color = Colors::colorFromValue(value, 0., max_val); // 0..50m
painter.fillRect(r, fill_color);
}
// areas "outside" are drawn as gray.
if (hgv.isForestOutside()) {
QRect r = vp.toScreen(domGrid->cellRect(p));
if (hgv.isRadiating())
painter.fillRect(r, Qt::gray);
else
painter.fillRect(r, QColor(240,240,240));
}
}
}
} // if (show_dom)
if (show_ru){
QString ru_expr = ui->lTreeExpr->text();
if (ru_expr.isEmpty())
ru_expr = "id";
RUWrapper ru_wrapper;
Expression ru_value(ru_expr, &ru_wrapper);
ru_value.setCatchExceptions(); // silent catching...
species_color = ui->visRUSpeciesColor->isChecked();
const Species *drawspecies=0;
GridViewType view_type = GridViewRainbow;
if (species_color) {
drawspecies = model->speciesSet()->species(species);
view_type = GridViewGreens;
}
double min_value = 0.;
double max_value = 1.; // defaults
double value;
if (!mRulerColors->autoScale()) {
max_value = mRulerColors->maxValue();
min_value = mRulerColors->minValue();
} else if (auto_scale_color) {
min_value = 9999999999999999999.;
max_value = -999999999999999999.;
foreach (const ResourceUnit *ru, model->ruList()) {
if (species_color && drawspecies) {
value = ru->constResourceUnitSpecies(drawspecies)->constStatistics().basalArea();
} else {
ru_wrapper.setResourceUnit(ru);
value = ru_value.execute();
}
min_value = qMin(min_value, value);
max_value = qMax(max_value, value);
}
qDebug() << "scale colors: min" << min_value << "max:" << max_value;
}
if (species_color) {
if (drawspecies) {
drawspecies = model->speciesSet()->species(species);
mRulerColors->setCaption("Species share", QString("Species: '%1'").arg(species));
mRulerColors->setPalette(GridViewGreens, static_cast<float>(min_value), static_cast<float>(max_value)); // ruler
} else {
mRulerColors->setCaption("Dominant species on resource unit", "The color indicates the species with the biggest share of basal area. \nDashed fill, if the basal area of the max-species is <50%.");
QList<const Species*> specieslist=mRemoteControl.availableSpecies();
QStringList colors; QStringList speciesnames;
for (int i=0; i<specieslist.count();++i) {
colors.append(specieslist[i]->displayColor().name());
speciesnames.append(specieslist[i]->name());
}
mRulerColors->setFactorColors(colors);
mRulerColors->setFactorLabels(speciesnames);
mRulerColors->setPalette(GridViewCustom, 0., 1.);
}
} else {
mRulerColors->setCaption("Resource Units", QString("Result of expression: '%1'").arg(ru_expr));
mRulerColors->setPalette(GridViewRainbow, static_cast<float>(min_value), static_cast<float>(max_value)); // ruler
}
// paint resource units
painter.setPen(Qt::black);
foreach (const ResourceUnit *ru, model->ruList()) {
bool stroke = false;
if (species_color) {
if (drawspecies) {
value = ru->constResourceUnitSpecies(drawspecies)->constStatistics().basalArea();
fill_color = Colors::colorFromValue(static_cast<float>(value), view_type, static_cast<float>(min_value), static_cast<float>(max_value));
} else {
const Species *max_sp=0; double max_ba = 0.; double total_ba=0.;
foreach(const ResourceUnitSpecies *rus, ru->ruSpecies()) {
total_ba += rus->constStatistics().basalArea();
if (rus->constStatistics().basalArea()>max_ba) {
max_ba = rus->constStatistics().basalArea();
max_sp=rus->species();
}
}
if (max_sp) {
fill_color = max_sp->displayColor();
if (max_ba < total_ba*0.5) {
stroke = true;
}
} else
fill_color = Qt::white;
}
} else {
ru_wrapper.setResourceUnit(ru);
value = ru_value.execute();
fill_color = Colors::colorFromValue(static_cast<float>(value), view_type, static_cast<float>(min_value), static_cast<float>(max_value));
}
QRect r = vp.toScreen(ru->boundingBox());
//fill_color = Colors::colorFromValue(value, min_value, max_value);
//fill_color = Colors::colorFromValue(static_cast<float>(value), view_type, static_cast<float>(min_value), static_cast<float>(max_value));
if (stroke)
painter.fillRect(r, QBrush(fill_color, Qt::Dense3Pattern));
else
painter.fillRect(r, fill_color);
}
if (!ru_value.lastError().isEmpty())
qDebug() << "Expression error while painting: " << ru_value.lastError();
}
if (show_trees) {
QString single_tree_expr = ui->lTreeExpr->text();
if (single_tree_expr.isEmpty())
single_tree_expr = "1-lri";
TreeWrapper tw;
Expression tree_value(single_tree_expr, &tw); // get maximum value
tree_value.setCatchExceptions(); // silent catching...
QString filter_expr = ui->expressionFilter->text();
bool do_filter = ui->cbDrawFiltered->isChecked();
if (filter_expr.isEmpty())
filter_expr = "1"; // a constant, always true
Expression tree_filter(filter_expr, &tw);
tree_filter.setCatchExceptions();
bool draw_transparent = ui->drawTransparent->isChecked();
AllTreeIterator treelist(model);
Tree *tree;
painter.setPen(Qt::gray);
double max_val=1., min_val=0.;
if (!mRulerColors->autoScale()) {
max_val = mRulerColors->maxValue(); min_val = mRulerColors->minValue();
}
while ((tree = treelist.next())) {
if ( !vp.isVisible(treelist.currentRU()->boundingBox()) ) {
continue;
}
// filter species...
if (!species.isEmpty())
if (tree->species()->id() != species)
continue;
// filter out dead trees
if (tree->isDead())
continue;
// filter (user defined)
if (do_filter) {
tw.setTree(tree);
if (tree_filter.execute()==0.)
continue;
}
QPointF pos = tree->position();
QPoint p = vp.toScreen(pos);
if (species_color) {
// use species specific color....
fill_color = tree->species()->displayColor();
} else {
// calculate expression
tw.setTree(tree);
value = static_cast<float>(tree_value.execute());
fill_color = Colors::colorFromValue(value, static_cast<float>(min_val), static_cast<float>(max_val), false);
}
if (draw_transparent)
fill_color.setAlpha(80); // 50%
painter.setBrush(fill_color);
int diameter = qMax(1,vp.meterToPixel( tree->crownRadius()));
painter.drawEllipse(p, diameter, diameter);
}
if (!tree_value.lastError().isEmpty())
qDebug() << "Expression error while painting: " << tree_value.lastError();
// ruler
if (species_color) {
mRulerColors->setCaption("Single trees", "species specific colors.");
QList<const Species*> specieslist=mRemoteControl.availableSpecies();
QStringList colors; QStringList speciesnames;
for (int i=0; i<specieslist.count();++i) {
colors.append(specieslist[i]->displayColor().name());
speciesnames.append(specieslist[i]->name());
}
mRulerColors->setFactorColors(colors);
mRulerColors->setFactorLabels(speciesnames);
mRulerColors->setPalette(GridViewCustom, 0., 1.);
} else {
mRulerColors->setCaption("Single trees", QString("result of expression: '%1'").arg(single_tree_expr));
mRulerColors->setPalette(GridViewRainbow, 0., 1.);
}
} // if (show_trees)
// highlight selected tree
Tree *t = reinterpret_cast<Tree*>( ui->treeChange->property("tree").toLongLong() );
if (t) {
QPointF pos = t->position();
painter.setPen(Qt::black);
QPoint p = vp.toScreen(pos);
painter.drawRect( p.x()-1, p.y()-1, 3,3);
}
}
void MainWindow::paintGrid(QPainter &painter, PaintObject &object)
{
painter.fillRect(ui->PaintWidget->rect(), object.background_color);
bool clip_with_stand_grid = ui->visClipStandGrid->isChecked();
bool shading = ui->visShading->isChecked();
if (!mRemoteControl.model() || !mRemoteControl.model()->dem())
shading=false;
int sx=0, sy=0;
QRect total_rect;
object.cur_min_value = object.min_value;
object.cur_max_value = object.max_value;
switch (object.what) {
case PaintObject::PaintMapGrid:
sx = object.map_grid->grid().sizeX();
sy = object.map_grid->grid().sizeY();
total_rect = vp.toScreen(object.map_grid->grid().metricRect());
mRulerColors->setCaption("Map grid");
break;
case PaintObject::PaintFloatGrid:
sx = object.float_grid->sizeX();
sy = object.float_grid->sizeY();
total_rect = vp.toScreen(object.float_grid->metricRect());
mRulerColors->setCaption("Floating point grid");
break;
case PaintObject::PaintLayers:
sx = object.layered->sizeX();
sy = object.layered->sizeY();
total_rect = vp.toScreen(object.layered->metricRect());
if (object.auto_range) {
object.layered->range( object.cur_min_value, object.cur_max_value, object.layer_id );
}
mRulerColors->setCaption(const_cast<LayeredGridBase*>(object.layered)->names()[object.layer_id].name,
const_cast<LayeredGridBase*>(object.layered)->names()[object.layer_id].description);
break;
case PaintObject::PaintNothing:
mRulerColors->setCaption("-");
return;
default: return;
}
if (!mRulerColors->autoScale()) {
object.cur_max_value = mRulerColors->maxValue();
object.cur_min_value = mRulerColors->minValue();
}
painter.setPen(Qt::black);
painter.drawRect(total_rect);
int ix,iy;
double value=0.;
QRect r;
QColor fill_color;
double max_value = -1.;
QPointF pmetric;
for (iy=0;iy<sy;iy++) {
for (ix=0;ix<sx;ix++) {
QPoint p(ix,iy);
switch(object.what) {
case PaintObject::PaintMapGrid:
value = object.map_grid->grid().constValueAtIndex(p);
pmetric = object.map_grid->grid().cellRect(p).center();
r = vp.toScreen(object.map_grid->grid().cellRect(p));
break;
case PaintObject::PaintFloatGrid:
value = object.float_grid->constValueAtIndex(p);
pmetric = object.float_grid->cellRect(p).center();
r = vp.toScreen(object.float_grid->cellRect(p));
break;
case PaintObject::PaintLayers:
value = object.layered->value(ix, iy, object.layer_id);
pmetric = object.layered->cellRect(p).center();
max_value = qMax(max_value, value);
r = vp.toScreen(object.layered->cellRect(p));
break;
default: ;
}
if (clip_with_stand_grid && !GlobalSettings::instance()->model()->heightGrid()->valueAt(pmetric).isValid()) {
fill_color = Qt::white;
} else {
fill_color = Colors::colorFromValue(value, object.view_type, object.cur_min_value, object.cur_max_value);
if (shading)
fill_color = Colors::shadeColor(fill_color, pmetric, mRemoteControl.model()->dem());
}
painter.fillRect(r, fill_color);
}
}
// update ruler
if (object.view_type>=10) {
QStringList labels;
for (int i=0;i<max_value;++i)
labels.append(object.layered->labelvalue(i,object.layer_id));
mRulerColors->setFactorLabels(labels);
}
mRulerColors->setPalette(object.view_type, object.cur_min_value, object.cur_max_value); // ruler
}
// paint the values of the MapGrid
void MainWindow::paintMapGrid(QPainter &painter,
MapGrid *map_grid, const FloatGrid *float_grid,
const GridViewType view_type,
double min_val, double max_val)
{
// clear background
painter.fillRect(ui->PaintWidget->rect(), Qt::white);
const Grid<int> *int_grid = 0;
int sx, sy;
QRect total_rect;
if (map_grid) {
int_grid = &map_grid->grid();
sx = int_grid->sizeX();
sy = int_grid->sizeY();
total_rect = vp.toScreen(int_grid->metricRect());
} else {
if (!float_grid)
return;
sx = float_grid->sizeX();
sy = float_grid->sizeY();
total_rect = vp.toScreen(float_grid->metricRect());
}
// draw rectangle around the grid
painter.setPen(Qt::black);
painter.drawRect(total_rect);
// paint the lower-res-grid;
int ix,iy;
double value;
QRect r;
QColor fill_color;
if (view_type<10)
mRulerColors->setPalette(view_type,min_val, max_val); // ruler
else
mRulerColors->setPalette(view_type, 0, max_val); // ruler
bool reverse = view_type == GridViewRainbowReverse || view_type == GridViewGrayReverse;
bool black_white = view_type == GridViewGray || view_type == GridViewGrayReverse;
for (iy=0;iy<sy;iy++) {
for (ix=0;ix<sx;ix++) {
QPoint p(ix,iy);
if (int_grid){
value = int_grid->constValueAtIndex(p);
r = vp.toScreen(int_grid->cellRect(p));
} else {
value = float_grid->constValueAtIndex(p);
r = vp.toScreen(float_grid->cellRect(p));
}
fill_color = view_type<10? Colors::colorFromValue(value, min_val, max_val, reverse,black_white) : Colors::colorFromPalette(value, view_type);
painter.fillRect(r, fill_color);
}
}
}
void MainWindow::repaintArea(QPainter &painter)
{
paintFON(painter, ui->PaintWidget->rect());
// fix viewpoint
vp.setScreenRect(ui->PaintWidget->rect());
mRulerColors->setScale(vp.pixelToMeter(1));
}
void MainWindow::on_visFon_toggled() { ui->PaintWidget->update(); }
void MainWindow::on_visDomGrid_toggled() { on_visFon_toggled(); }
void MainWindow::on_visImpact_toggled() { on_visFon_toggled(); }
bool wantDrag=false;
void MainWindow::mouseClick(const QPoint& pos)
{
if (!mRemoteControl.canRun())
return;
QPointF coord=vp.toWorld(pos);
//qDebug() << "to world:" << coord;
wantDrag = false;
ui->PaintWidget->setCursor(Qt::CrossCursor);
Model *model = mRemoteControl.model();
ResourceUnit *ru = model->ru(coord);
// find adjactent tree
// test ressource units...
if (ui->visResourceUnits->isChecked()) {
if (!ru) return;
showResourceUnitDetails(ru);
return;
}
// test for ABE grid
if (ui->visOtherGrid->isChecked()) {
if (showABEDetails(coord))
return;
}
//qDebug() << "coord:" << coord << "RU:"<< ru << "ru-rect:" << ru->boundingBox();
if (!ru)
return;
ui->treeChange->setProperty("tree",0);
QVector<Tree> &mTrees = ru->trees();
QVector<Tree>::iterator tit;
Tree *closestTree=0;
double min_distance = 100000000, current_dist;
for (tit=mTrees.begin(); tit!=mTrees.end(); ++tit) {
current_dist = distance(tit->position(),coord);
if (current_dist<min_distance) {
closestTree = &(*tit);
min_distance = current_dist;
}
}
if (min_distance<5 && closestTree) {
Tree *p = closestTree;
//qDebug() << "found!" << tit->id() << "at" << tit->position()<<"value"<<p->lightResourceIndex();
//qDebug() <<p->dump();
showTreeDetails(p);
ui->treeChange->setProperty("tree", qVariantFromValue((void*)p));
ui->treeDbh->setValue(p->dbh());
ui->treeHeight->setValue(p->height());
ui->treePosX->setValue(p->position().x());
ui->treePosY->setValue(p->position().y());
ui->treeImpact->setText(QString("#:%1 - %2").arg(p->id()).arg(p->lightResourceIndex(),5));
wantDrag=true;
ui->PaintWidget->setCursor(Qt::SizeAllCursor);
ui->PaintWidget->update();
}
}
void MainWindow::showResourceUnitDetails(const ResourceUnit *ru)
{
ui->dataTree->clear();
RUWrapper ruw;
ruw.setResourceUnit(ru);
const QStringList &names = ruw.getVariablesList();
QList<QTreeWidgetItem *> items;
foreach(QString name, names) {
items.append(new QTreeWidgetItem(QStringList()<<name<<QString::number(ruw.valueByName(name)) ));
}
// add special values (strings)
if (ru->climate())
items.append(new QTreeWidgetItem(QStringList()<<"climate"<<ru->climate()->name() ));
QList<QPair<QString, QVariant> > dbgdata = GlobalSettings::instance()->debugValues(-ru->index()); // hack: use negative values for resource units
QList<QPair<QString, QVariant> >::const_iterator i = dbgdata.constBegin();
while (i != dbgdata.constEnd()) {
//cout << i.key() << ": " << i.value() << endl;
items.append(new QTreeWidgetItem(QStringList()
<< (*i).first
<< (*i).second.toString()) );
++i;
}
ui->dataTree->addTopLevelItems(items);
}
bool MainWindow::showABEDetails(const QPointF &coord)
{
if (!mRemoteControl.canRun()) return false;
if (mPaintNext.layered) {
if (mPaintNext.layered->onClick(coord))
return true;
}
if (!mPaintNext.layered || !mRemoteControl.model()->ABEngine()) return false;
QString grid_name = mPaintNext.name;
QStringList list = mRemoteControl.model()->ABEngine()->evaluateClick(coord, grid_name);
ui->dataTree->clear();
QList<QTreeWidgetItem *> items;
QStack<QTreeWidgetItem*> stack;
stack.push(0);
foreach (QString s, list) {
QStringList elem = s.split(":");
if (s=="-")
stack.push(items.back());
else if (s=="/-")
stack.pop();
else {
items.append( new QTreeWidgetItem(stack.last(), elem) );
}
}
ui->dataTree->addTopLevelItems(items);
return true; // handled
}
void MainWindow::showTreeDetails(Tree *tree)
{
ui->dataTree->clear();
TreeWrapper tw;
tw.setTree(tree);
const QStringList &names = tw.getVariablesList();
QList<QTreeWidgetItem *> items;
foreach(QString name, names) {
if (name=="species")
items.append(new QTreeWidgetItem(QStringList() << name << mRemoteControl.model()->speciesSet()->species(tw.valueByName(name))->id() ));
else
items.append(new QTreeWidgetItem(QStringList() << name << QString::number(tw.valueByName(name)) ));
}
QList<QPair<QString, QVariant> > dbgdata = GlobalSettings::instance()->debugValues(tree->id());
QList<QPair<QString, QVariant> >::const_iterator i = dbgdata.constBegin();
while (i != dbgdata.constEnd()) {
//cout << i.key() << ": " << i.value() << endl;
items.append(new QTreeWidgetItem(QStringList()
<< (*i).first
<< (*i).second.toString()) );
++i;
}
ui->dataTree->addTopLevelItems(items);
}
void MainWindow::mouseMove(const QPoint& pos)
{
if (!mRemoteControl.canRun() )
return;
FloatGrid *grid = mRemoteControl.model()->grid();
QPointF p = vp.toWorld(pos);
bool has_value = true;
double value;
if (grid->coordValid(p)) {
QString location=QString("%1 / %2").arg(p.x()).arg(p.y());
if (ui->visOtherGrid->isChecked()) {
switch (mPaintNext.what) {
case PaintObject::PaintFloatGrid:
value = mPaintNext.float_grid->isEmpty()?0: mPaintNext.float_grid->constValueAt(p);
break;
case PaintObject::PaintMapGrid:
value = mPaintNext.map_grid->grid().constValueAt(p);
break;
case PaintObject::PaintLayers:
value = mPaintNext.layered->value(p, mPaintNext.layer_id);
if (mPaintNext.view_type>=10) {// classes
location += QString("\n %1").arg(mPaintNext.layered->labelvalue(value, mPaintNext.layer_id));
ui->fonValue->setText(location);
return;
}
break;
default: has_value = false;
}
if (has_value) {
location += QString("\n %1").arg(value);
ui->fonValue->setText(location);
return;
}
}
if (ui->visFon->isChecked() || ui->visImpact->isChecked()) {
if (mPaintNext.what == PaintObject::PaintFloatGrid && mPaintNext.float_grid)
location += QString("\n %1").arg(mPaintNext.float_grid->constValueAt(p));
else
location += QString("\n %1").arg((*grid).valueAt(p));
}
if( ui->visDomGrid->isChecked())
location += QString("\n %1").arg((*mRemoteControl.model()->heightGrid()).valueAt(p).height);
if( ui->visRegeneration->isChecked() && !mRegenerationGrid.isEmpty())
location += QString("\n %1").arg(mRegenerationGrid.valueAt(p));
if (ui->visSeeds->isChecked() && ui->speciesFilterBox->currentIndex()>-1) {
Species *s=GlobalSettings::instance()->model()->speciesSet()->species(ui->speciesFilterBox->itemData(ui->speciesFilterBox->currentIndex()).toString());
if (s && s->seedDispersal())
location += QString("\n %1").arg(s->seedDispersal()->seedMap().constValueAt(p));
}
ui->fonValue->setText(location);
}
}
void MainWindow::mouseWheel(const QPoint& pos, int steps)
{
//qDebug() << "mouse-wheel" << steps;
vp.zoomTo(pos, qMax(1-(2*steps/10.),0.2));
ui->PaintWidget->update();
}
void MainWindow::mouseDrag(const QPoint& from, const QPoint &to, Qt::MouseButton button)
{
qDebug() << "drag" << button;
ui->PaintWidget->setCursor(Qt::CrossCursor);
// move view area if not dedicately moving around a tree
if (!wantDrag) {
vp.moveTo(from, to);
ui->PaintWidget->update();
return;
}
wantDrag = false;
qDebug() << "drag from" << from << "to" << to;
Tree *t = reinterpret_cast<Tree*>( ui->treeChange->property("tree").toLongLong() );
if (!t)
return;
QPointF pos = vp.toWorld(to);
// calculate new position...
t->setPosition(pos);
readwriteCycle();
ui->PaintWidget->update();
}
void MainWindow::on_actionEdit_XML_settings_triggered()
{
ui->editStack->setCurrentIndex(0);
ui->PaintWidget->update();
}
QMutex mutex_yearSimulated;
void MainWindow::yearSimulated(int year)
{
QMutexLocker mutex_locker(&mutex_yearSimulated);
checkModelState();
ui->modelRunProgress->setValue(year);
labelMessage(QString("Running.... year %1 of %2.").arg(year).arg(mRemoteControl.totalYears()));
ui->treeChange->setProperty("tree",0);
ui->PaintWidget->update();
QApplication::processEvents();
}
void MainWindow::modelFinished(QString errorMessage)
{
if (!errorMessage.isEmpty()) {
Helper::msg(errorMessage);
labelMessage("Error!");
qDebug() << "Error:" << errorMessage;
} else {
qDebug() << "Finished!";
labelMessage("Finished!!");
}
checkModelState();
if (windowTitle().contains("batch")) {
// we are in automatic batch mode.
// we should therefore close down the application.
if (!errorMessage.isEmpty())
batchLog(QString("error: %1").arg(errorMessage));
batchLog(QString("%1 Finished!!! shutting down...").arg(QDateTime::currentDateTime().toString(Qt::ISODate)));
qDebug() << "****************************";
qDebug() << "Finished automated model run: " << errorMessage;
qDebug() << "****************************";
close(); // shut down the application....
}
}
/// creates the iLand model
void MainWindow::setupModel()
{
//recent file menu
recentFileMenu();
// load project xml file to global xml settings structure
mRemoteControl.setFileName(ui->initFileName->text());
//GlobalSettings::instance()->loadProjectFile(ui->initFileName->text());
labelMessage("Creating model...");
// setup logging
setupFileLogging(true);
// create the model
mRemoteControl.create();
if (!mRemoteControl.canRun())
return;
Model *model = mRemoteControl.model();
if (model && model->isSetup()) {
// set viewport of paintwidget
vp = Viewport(model->grid()->metricRect(), ui->PaintWidget->rect());
ui->PaintWidget->update();
}
ui->treeChange->setProperty("tree",0);
// setup dynamic output
QString dout = GlobalSettings::instance()->settings().value("output.dynamic.columns");
mRemoteControl.setupDynamicOutput(dout);
mRemoteControl.setDynamicOutputEnabled(GlobalSettings::instance()->settings().valueBool("output.dynamic.enabled",false));
ui->modelRunProgress->setValue(0);
QSettings().setValue("project/lastxmlfile", ui->initFileName->text());
// magic debug output number
GlobalSettings::instance()->setDebugOutput((int) GlobalSettings::instance()->settings().valueDouble("system.settings.debugOutput"));
// populate the tree species filter list
ui->speciesFilterBox->clear();
ui->speciesFilterBox->addItem("<all species>", "");
QList<const Species*> list = mRemoteControl.availableSpecies();
for (int i=0;i<list.size();++i)
ui->speciesFilterBox->addItem(list[i]->name(), list[i]->id());
// retrieve the active management script file
if (mRemoteControl.model()->management())
ui->scriptActiveScriptFile->setText(QString("%1").arg(mRemoteControl.model()->management()->scriptFile()));
if (!mRemoteControl.loadedJavascriptFile().isEmpty())
ui->scriptActiveScriptFile->setText(QString("%1").arg(mRemoteControl.loadedJavascriptFile()));
labelMessage("Model created. Ready to run.");
checkModelState();
}
void MainWindow::on_pbSetAsDebug_clicked()
{
Tree *t = reinterpret_cast<Tree *>( ui->treeChange->property("tree").toLongLong() );
if (!t)
return;
t->enableDebugging();
}
void MainWindow::on_openFile_clicked()
{
QString fileName = Helper::fileDialog("select XML-project file", ui->initFileName->text(), "*.xml",this);
if (fileName.isEmpty())
return;
ui->initFileName->setText(fileName);
QString xmlFile = Helper::loadTextFile(ui->initFileName->text());
ui->iniEdit->setPlainText(xmlFile);
checkModelState();
}
void MainWindow::on_actionTreelist_triggered()
{
QApplication::clipboard()->setText(dumpTreelist());
qDebug() << "treelist copied to clipboard.";
}
void MainWindow::on_actionFON_grid_triggered()
{
//if (!mRemoteControl.isRunning()) return;
QString gr = gridToString(*mRemoteControl.model()->grid());
QApplication::clipboard()->setText(gr);
qDebug() << "grid copied to clipboard.";
}
void MainWindow::on_actionModelCreate_triggered()
{
// create model
setupModel();
checkModelState();
}
void MainWindow::on_actionModelDestroy_triggered()
{
mPaintNext.what = PaintObject::PaintNothing;
mRemoteControl.destroy();
mRegenerationGrid.clear();
checkModelState();
}
void MainWindow::on_actionModelRun_triggered()
{
if (!mRemoteControl.canRun())
return;
QString msg = QString("How many years to run?\nCurrent year: %1.").arg(mRemoteControl.currentYear());
bool ok;
int count = QInputDialog::getInt(this, "input value",
msg, 10, 0, 10000, 1, &ok);
if (!ok)
return;
count = count + mRemoteControl.currentYear();
ui->treeChange->setProperty("tree",0);
ui->modelRunProgress->setMaximum(count-1);
mRemoteControl.run(count);
GlobalSettings::instance()->executeJSFunction("onAfterRun");
}
void MainWindow::on_actionRun_one_year_triggered()
{
if (!mRemoteControl.canRun())
return;
ui->treeChange->setProperty("tree",0);
mRemoteControl.runYear();
GlobalSettings::instance()->outputManager()->save(); // save output tables when stepping single year by year
labelMessage(QString("Simulated a single year. year %1.").arg(mRemoteControl.currentYear()));
ui->PaintWidget->update();
checkModelState();
}
void MainWindow::on_actionReload_triggered()
{
if (!mRemoteControl.canDestroy())
return;
mPaintNext.what = PaintObject::PaintNothing;
mRemoteControl.destroy();
mRegenerationGrid.clear();
setupModel();
}
void MainWindow::on_actionPause_triggered()
{
mRemoteControl.pause();
if (!mRemoteControl.isRunning())
labelMessage("Model execution paused...");
checkModelState();
if (!mRemoteControl.isPaused())
mRemoteControl.continueRun();
}
void MainWindow::on_actionStop_triggered()
{
mRemoteControl.cancel();
labelMessage("Model stopped.");
}
void MainWindow::on_actionTree_Partition_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreePartition, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_action_debugSapling_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dSaplingGrowth, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_actionTree_Growth_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreeGrowth, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_actionTree_NPP_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreeNPP, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_actionWater_Output_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dWaterCycle, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_actionDaily_responses_Output_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dDailyResponses, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_action_debugEstablishment_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dEstablishment, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_actionSnag_Dynamics_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dCarbonCycle, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
void MainWindow::on_actionPerformance_triggered()
{
QStringList result = GlobalSettings::instance()->debugDataTable(GlobalSettings::dPerformance, ";");
QApplication::clipboard()->setText(result.join("\n"));
qDebug() << "copied" << result.count() << "lines of debug data to clipboard.";
}
QImage MainWindow::screenshot()
{
return ui->PaintWidget->drawImage();
}
/// set the viewport of the main viewing window
/// @p center_point is the point to zoom to (world coordinates), and @p scael_px_per_m is the
/// pixel/m scaling.
void MainWindow::setViewport(QPointF center_point, double scale_px_per_m)
{
if (scale_px_per_m>0.)
vp.setViewPoint(center_point, scale_px_per_m);
else
vp.zoomToAll();
// double current_px = vp.pixelToMeter(1); // number of meters covered by one pixel
// if (current_px==0)
// return;
// vp.setCenterPoint(center_point);
// QPoint screen = vp.toScreen(center_point); // screen coordinates of the target point
// double target_scale = scale_px_per_m / current_px;
// vp.zoomTo(screen, target_scale);
ui->PaintWidget->update();
QCoreApplication::processEvents();
}
void MainWindow::setUIshortcuts(QVariantMap shortcuts)
{
if (shortcuts.isEmpty()) {
ui->lJSShortcuts->setText("(no shortcuts defined)");
return;
}
QString msg = "<html><head/><body><p>Javascript shortcuts<br>";
QVariantMap::const_iterator i;
for (i = shortcuts.constBegin(); i != shortcuts.constEnd(); ++i) {
QString line = QString("<a href =\"%1\"><span style=\" text-decoration: underline; color:#0000ff;\">%1</span></a>: %2<br>").arg(i.key(), i.value().toString());
msg += line;
}
msg += "</body></html>";
//qDebug() << msg;
ui->lJSShortcuts->setText(msg);
ui->lJSShortcuts->setTextInteractionFlags(Qt::TextBrowserInteraction);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
writeSettings();
event->accept();
}
void MainWindow::on_actionImageToClipboard_triggered()
{
//QClipboard *clipboard = QApplication::clipboard();
QImage my_img = screenshot();
my_img.convertToFormat(QImage::Format_RGB32);
//clipboard->setImage( my_img, QClipboard::Clipboard );
QString pth = GlobalSettings::instance()->path("screenshot.png", "temp");
screenshot().save(pth);
qDebug() << "copied image to clipboard. save also to: " << pth;
my_img.load(pth);
QApplication::clipboard()->setImage(my_img);
}
void MainWindow::on_actionSelect_Data_Types_triggered()
{
int value = GlobalSettings::instance()->currentDebugOutput();
int newvalue = QInputDialog::getInt(this, "QInputDialog::getText()",
"Enter code for desired outputs: add\n" \
"1 ... Tree NPP\n" \
"2 ... Tree partition\n" \
"4 ... Tree growth (dbh,h)\n" \
"8 ... Standlevel GPP\n" \
"16...Water Cycle\n" \
"32...Daily responses\n" \
"64...Establishment\n" \
"128...Sapling growth\n" \
"256...Carbon cycle\n" \
"512...Performance\n"
"(e.g.: 5 = NPP + tree growth) or 0 for no debug outputs.", value);
GlobalSettings::instance()->setDebugOutput(newvalue);
}
// Expression test
void MainWindow::on_pbCalculateExpression_clicked()
{
QString expr_text=ui->expressionText->text();
QString expr_filter=ui->expressionFilter->text();
if (expr_text == "test") {
on_actionTest_triggered();
return;
}
if (expr_filter.isEmpty())
expr_filter = "1"; // a constant true expression
TreeWrapper wrapper;
Expression expr(expr_text, &wrapper);
Expression filter(expr_filter, &wrapper);
AllTreeIterator at(GlobalSettings::instance()->model());
int totalcount=0;
QVector<double> datavector;
try {
while (Tree *tree=at.next()) {
wrapper.setTree(tree);
if (filter.execute()) {
datavector << expr.execute();
}
totalcount++;
}
} catch (IException &e) {
Helper::msg(e.message());
}
StatData stats(datavector);
qDebug() << "Expression:" << expr_text << "filtered" << datavector.count() << "of" << totalcount;
qDebug() << "sum:" << stats.sum() << "min" << stats.min() << "max" << stats.max() << "average" << stats.mean();
qDebug() << "P25" << stats.percentile25() << "median" << stats.median() << "P75" << stats.percentile75() << "P90" << stats.percentile(90);
//qDebug() << "Expression:" << expr_text << "results: count of total: " << count << "/" << totalcount
// << "sum:" << sum << "average:" << (count>0?sum/double(count):0.) << "minval:" << minval << "maxval:" << maxval;
// add to history
if (!ui->expressionHistory->currentItem() || ui->expressionHistory->currentItem()->text() != expr_text) {
ui->expressionHistory->insertItem(0, expr_text);
ui->expressionHistory->setCurrentRow(0);
}
}
void MainWindow::on_pbExecExpression_clicked()
{
// just repaint...
ui->PaintWidget->update();
}
void MainWindow::on_actionDynamic_Output_triggered()
{
QApplication::clipboard()->setText(mRemoteControl.dynamicOutput());
qDebug() << "copied dynamic output to clipboard";
}
void MainWindow::on_actionShow_Debug_Messages_triggered(bool checked)
{
// enable/disble debug messages
showDebugMessages=checked;
}
void MainWindow::on_reloadJavaScript_clicked()
{
if (!GlobalSettings::instance()->model())
MSGRETURN("no model available.");
ScriptGlobal::loadScript(ui->scriptActiveScriptFile->text());
ScriptGlobal::scriptOutput = ui->scriptResult;
}
void MainWindow::on_selectJavaScript_clicked()
{
if (!GlobalSettings::instance()->model())
return;
QString fileName = Helper::fileDialog("select a Javascript file:");
if (fileName.isEmpty())
return;
ScriptGlobal::loadScript(fileName);
ui->scriptActiveScriptFile->setText(QString("%1").arg(fileName));
qDebug() << "loaded Javascript file" << fileName;
ScriptGlobal::scriptOutput = ui->scriptResult;
}
void MainWindow::on_scriptCommand_returnPressed()
{
QString command = ui->scriptCommand->text();
if (ui->scriptCommandHistory->currentText() != command) {
ui->scriptCommandHistory->insertItem(0, command);
ui->scriptCommandHistory->setCurrentIndex(0);
}
qDebug() << "executing" << command;
try {
QString result = ScriptGlobal::executeScript(command);
if (!result.isEmpty()) {
ui->scriptResult->append(result);
qDebug() << result;
}
} catch(const IException &e) {
Helper::msg(e.message());
}
}
void MainWindow::on_actionOutput_table_description_triggered()
{
QString txt = GlobalSettings::instance()->outputManager()->wikiFormat();
QApplication::clipboard()->setText(txt);
qDebug() << "Description copied to clipboard!";
}
void MainWindow::on_actionTimers_triggered()
{
LogToWindow l;
DebugTimer::printAllTimers();
}
void MainWindow::on_actionOnline_ressources_triggered()
{
QDesktopServices::openUrl(QUrl("http://iland.boku.ac.at/"));
}
void MainWindow::on_actionAbout_triggered()
{
AboutDialog dialog;
dialog.exec();
}
/* Logging and filtering of logging */
void MainWindow::on_pbLogToClipboard_clicked()
{
// copy content of log window to clipboard
QApplication::clipboard()->setText(ui->logOutput->toPlainText());
}
void MainWindow::on_pbLogClearText_clicked()
{
ui->logOutput->clear();
ui->logOutput->setProperty("fullText","");
ui->pbLogFilterClear->setEnabled(false);
}
void MainWindow::on_pbFilterExecute_clicked()
{
QStringList lines;
QString search_for = ui->logFilterExpression->text();
if (search_for.isEmpty())
return;
QString full_content;
if (ui->logOutput->property("fullText").toString().isEmpty()) {
full_content = ui->logOutput->toPlainText();
ui->logOutput->setProperty("fullText",full_content);
} else
full_content = ui->logOutput->property("fullText").toString();
QStringList debugLines = full_content.split("\n");
int i=0;
foreach(const QString &line, debugLines) {
i++; // line counter
if (line.contains(search_for))
lines.push_back(QString("%1: %2").arg(i).arg(line) );
}
if (lines.count()>0) {
ui->logOutput->setPlainText(lines.join("\n"));
} else {
ui->logOutput->setPlainText("Search term not found!");
}
ui->pbLogFilterClear->setEnabled(true);
}
void MainWindow::on_pbLogFilterClear_clicked()
{
QString text = ui->logOutput->property("fullText").toString();
if (text.isEmpty())
return;
//QString sel = ui->logOutput->textCursor().selectedText();
//int line = sel.toInt();
int line = atoi(ui->logOutput->textCursor().block().text().toLocal8Bit());
ui->logOutput->setPlainText(text);
ui->logOutput->setProperty("fullText","");
//int bl = ui->logOutput->document()->findBlockByNumber(line).position();
ui->logOutput->setTextCursor(QTextCursor(ui->logOutput->document()->findBlockByNumber(line)));
ui->logOutput->ensureCursorVisible();
ui->pbLogFilterClear->setEnabled(false);
}
void MainWindow::on_actionClearDebugOutput_triggered()
{
GlobalSettings::instance()->clearDebugLists();
}
void MainWindow::on_actionDebug_triggered()
{
//
QObject *o = QObject::sender();
int level = o->property("logLevel").toInt();
ui->actionDebug->setChecked( level == 0);
ui->actionInfo->setChecked( level == 1);
ui->actionWarning->setChecked( level == 2);
ui->actionError->setChecked( level == 3);
setLogLevel(level);
}
void MainWindow::on_scriptCommandHistory_currentIndexChanged(int index)
{
if (index>=0)
ui->scriptCommand->setText(ui->scriptCommandHistory->itemText(index));
}
void MainWindow::writeSettings()
{
QSettings settings;
settings.beginGroup("MainWindow");
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
settings.endGroup();
// javascript commands
settings.beginWriteArray("javascriptCommands");
int size = qMin(ui->scriptCommandHistory->count(), 15); // max 15 entries in the history
for (int i=0;i<size; ++i) {
settings.setArrayIndex(i);
settings.setValue("item", ui->scriptCommandHistory->itemText(i));
}
settings.endArray();
settings.beginGroup("project");
settings.setValue("lastxmlfile", ui->initFileName->text());
settings.endGroup();
//recent files menu qsettings registry save
settings.beginGroup("recent_files");
for(int i = 0;i < mRecentFileList.size();i++){
settings.setValue(QString("file-%1").arg(i),mRecentFileList[i]);
}
settings.endGroup();
}
void MainWindow::readSettings()
{
QSettings::setDefaultFormat(QSettings::IniFormat);
QCoreApplication::setOrganizationName("iLand");
QCoreApplication::setOrganizationDomain("iland.boku.ac.at");
QCoreApplication::setApplicationName("iLand");
QSettings settings;
qDebug() << "reading settings from" << settings.fileName();
// window state and
restoreGeometry(settings.value("MainWindow/geometry").toByteArray());
restoreState(settings.value("MainWindow/windowState").toByteArray());
// read javascript commands
int size = settings.beginReadArray("javascriptCommands");
for (int i=0;i<size; ++i) {
settings.setArrayIndex(i);
ui->scriptCommandHistory->addItem(settings.value("item").toString());
}
settings.endArray();
//recent files menu qsettings registry load
settings.beginGroup("recent_files");
for(int i = 0;i < settings.childKeys().size();i++){
//resize(settings.value("size", QSize(400, 400)).toSize());
mRecentFileList.append(settings.value(QString("file-%1").arg(i)).toString());
}
for(int i = 0;i < ui->menuRecent_Files->actions().size();i++){
if(i < mRecentFileList.size()){
ui->menuRecent_Files->actions()[i]->setText(mRecentFileList[i]);
connect(ui->menuRecent_Files->actions()[i],SIGNAL(triggered()),this,SLOT(menuRecent_Files()));
ui->menuRecent_Files->actions()[i]->setVisible(true);
}else{
ui->menuRecent_Files->actions()[i]->setVisible(false);
}
}
settings.endGroup();
}
void MainWindow::on_paintGridBox_currentIndexChanged(int index)
{
Q_UNUSED(index);
ui->visOtherGrid->setChecked(true);
}
void MainWindow::on_actionTest_triggered()
{
Tests t(this);
int which = QInputDialog::getInt(this, "Which test",
"which test?\n0: expression speed\n1: tree clear\n" \
"2:kill trees\n3: climate\n4: multiple light automation\n" \
"5: species response\n" \
"6: watercycle\n" \
"7: CSV File\n" \
"8: Xml setters\n" \
"9: random functions\n" \
"10: seed dispersal.\n" \
"11: multiple thread expression\n" \
"12: linearized expressions\n" \
"13: establishment\n" \
"14: GridRunner\n" \
"15: Soil (ICBM/2N)\n" \
"16: load Map \n" \
"17: test DEM \n" \
"18: test fire module \n" \
"19: test wind module\n" \
"20: test rumple index\n" \
"21: test FOME setup\n" \
"22: test FOME step\n" \
"23: test debug establishment\n" \
"24: test grid special index hack",-1);
switch (which) {
case 0: t.speedOfExpression();break;
case 1: t.clearTrees(); break;
case 2: t.killTrees(); break;
case 3: t.climate(); break;
case 4: t.multipleLightRuns(GlobalSettings::instance()->path("automation.xml", "home"));
case 5: t.climateResponse(); break;
case 6: t.testWater(); break;
case 7: t.testCSVFile(); break;
case 8: t.testXml(); break;
case 9: t.testRandom(); break;
case 10: t.testSeedDispersal(); break;
case 11: t.testMultithreadExecute(); break;
case 12: t.testLinearExpressions(); break;
case 13: t.testEstablishment(); break;
case 14: t.testGridRunner(); break;
case 15: t.testSoil(); break;
case 16: t.testMap(); break;
case 17: t.testDEM(); break;
case 18: t.testFire(); break;
case 19: t.testWind(); break;
case 20: t.testRumple(); break;
case 21: t.testFOMEsetup(); break;
case 22: t.testFOMEstep(); break;
case 23: t.testDbgEstablishment(); break;
case 24: t.testGridIndexHack(); break;
}
}
void MainWindow::on_pbReloadQml_clicked()
{
//engine()->clearComponentCache();
//setSource(source());
if (!mRuler)
return;
mRuler->engine()->clearComponentCache();
mRuler->setSource(mRuler->source());
}
void MainWindow::on_actionExit_triggered()
{
if (Helper::question("Do you really want to quit?"))
close();
}
void MainWindow::on_actionOpen_triggered()
{
QString fileName = Helper::fileDialog("select XML-project file", ui->initFileName->text(), "*.xml");
if (fileName.isEmpty())
return;
ui->initFileName->setText(fileName);
QString xmlFile = Helper::loadTextFile(ui->initFileName->text());
ui->iniEdit->setPlainText(xmlFile);
checkModelState();
}
void MainWindow::menuRecent_Files()
{
QAction* action = dynamic_cast<QAction*>(sender());
if (action)
ui->initFileName->setText(action->text());
}
void MainWindow::recentFileMenu(){
if(mRecentFileList.size() > 9){
mRecentFileList.removeAt(9);
}
if(mRecentFileList.contains(ui->initFileName->text())){
mRecentFileList.removeAt(mRecentFileList.indexOf(ui->initFileName->text()));
}
mRecentFileList.prepend(ui->initFileName->text());
for(int i = 0;i < ui->menuRecent_Files->actions().size();i++){
if(i < mRecentFileList.size()){
ui->menuRecent_Files->actions()[i]->setText(mRecentFileList[i]);
connect(ui->menuRecent_Files->actions()[i],SIGNAL(triggered()),this,SLOT(menuRecent_Files()));
ui->menuRecent_Files->actions()[i]->setVisible(true);
}else{
ui->menuRecent_Files->actions()[i]->setVisible(false);
}
}
}
void MainWindow::on_saveFile_clicked()
{
ui->iniEdit->setVisible(!ui->iniEdit->isVisible());
}
void MainWindow::on_lJSShortcuts_linkActivated(const QString &link)
{
qDebug() << "executing: " << link;
try {
qDebug() << ScriptGlobal::executeScript(link);
} catch(const IException &e) {
Helper::msg(e.message());
}
}