Subversion Repositories public iLand

Rev

Rev 1196 | Rev 1217 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1
 
671 werner 2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
7
**    This program is free software: you can redistribute it and/or modify
8
**    it under the terms of the GNU General Public License as published by
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
11
**
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
16
**
17
**    You should have received a copy of the GNU General Public License
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
20
 
697 werner 21
/** ModelController is a helper class used to control the flow of operations during a model run.
22
  The ModelController encapsulates the Model class and is the main control unit. It is used by the
23
  iLand GUI as well as the command line version (ilandc).
24
 
105 Werner 25
  */
128 Werner 26
 
105 Werner 27
#include "global.h"
28
#include "modelcontroller.h"
128 Werner 29
#include <QObject>
105 Werner 30
 
31
#include "model.h"
808 werner 32
#include "debugtimer.h"
128 Werner 33
#include "helper.h"
1182 werner 34
#include "version.h"
165 werner 35
#include "expression.h"
161 werner 36
#include "expressionwrapper.h"
176 werner 37
#include "../output/outputmanager.h"
105 Werner 38
 
514 werner 39
#include "species.h"
40
#include "speciesset.h"
596 werner 41
#include "mapgrid.h"
808 werner 42
#include "statdata.h"
514 werner 43
 
678 werner 44
#ifdef ILAND_GUI
267 werner 45
#include "mainwindow.h" // for the debug message buffering
678 werner 46
#endif
267 werner 47
 
105 Werner 48
ModelController::ModelController()
49
{
128 Werner 50
    mModel = NULL;
223 werner 51
    mPaused = false;
225 werner 52
    mRunning = false;
837 werner 53
    mHasError = false;
223 werner 54
    mYearsToRun = 0;
590 werner 55
    mViewerWindow = 0;
802 werner 56
    mDynamicOutputEnabled = false;
105 Werner 57
}
128 Werner 58
 
59
ModelController::~ModelController()
60
{
61
    destroy();
62
}
63
 
590 werner 64
void ModelController::connectSignals()
65
{
66
    if (!mViewerWindow)
67
        return;
678 werner 68
#ifdef ILAND_GUI
590 werner 69
    connect(this,SIGNAL(bufferLogs(bool)), mViewerWindow, SLOT(bufferedLog(bool)));
678 werner 70
#endif
590 werner 71
}
72
 
514 werner 73
/// prepare a list of all (active) species
1066 werner 74
QList<const Species*> ModelController::availableSpecies()
514 werner 75
{
1066 werner 76
    QList<const Species*> list;
514 werner 77
    if (mModel) {
78
        SpeciesSet *set = mModel->speciesSet();
79
        if (!set)
80
            throw IException("there are 0 or more than one species sets.");
81
        foreach (const Species *s, set->activeSpecies()) {
1066 werner 82
            list.append(s);
514 werner 83
        }
84
    }
85
    return list;
86
}
128 Werner 87
 
145 Werner 88
bool ModelController::canCreate()
128 Werner 89
{
129 Werner 90
    if (mModel)
128 Werner 91
        return false;
92
    return true;
93
}
94
 
145 Werner 95
bool ModelController::canDestroy()
128 Werner 96
{
97
    return mModel != NULL;
98
}
99
 
145 Werner 100
bool ModelController::canRun()
128 Werner 101
{
102
    if (mModel && mModel->isSetup())
103
        return true;
104
    return false;
105
}
106
 
145 Werner 107
bool ModelController::isRunning()
128 Werner 108
{
225 werner 109
    return mRunning;
128 Werner 110
}
111
 
225 werner 112
bool ModelController::isFinished()
113
{
114
    if (!mModel)
115
        return false;
116
    return canRun() && !isRunning()  && mFinished;
497 werner 117
}
128 Werner 118
 
776 werner 119
bool ModelController::isPaused()
120
{
121
    return mPaused;
122
}
123
 
497 werner 124
int ModelController::currentYear() const
125
{
126
    return GlobalSettings::instance()->currentYear();
225 werner 127
}
128
 
128 Werner 129
void ModelController::setFileName(QString initFileName)
130
{
131
    mInitFile = initFileName;
132
    try {
133
        GlobalSettings::instance()->loadProjectFile(mInitFile);
134
    } catch(const IException &e) {
575 werner 135
        QString error_msg = e.message();
128 Werner 136
        Helper::msg(error_msg);
837 werner 137
        mHasError = true;
138
        mLastError = error_msg;
128 Werner 139
        qDebug() << error_msg;
140
    }
141
}
142
 
143
void ModelController::create()
144
{
145
    if (!canCreate())
146
        return;
590 werner 147
    emit bufferLogs(true);
1204 werner 148
    qDebug() << "**************************************************";
149
    qDebug() << "project-file:" << mInitFile;
150
    qDebug() << "started at: " << QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss");
151
    qDebug() << "iLand " << currentVersion() << " (" << svnRevision() << ")";
152
    qDebug() << "**************************************************";
590 werner 153
 
1204 werner 154
 
128 Werner 155
    try {
837 werner 156
        mHasError = false;
285 werner 157
        DebugTimer::clearAllTimers();
158
        mModel = new Model();
159
        mModel->loadProject();
837 werner 160
        if (!mModel->isSetup()) {
161
            mHasError = true;
162
            mLastError = "An error occured during the loading of the project. Please check the logs.";
286 werner 163
            return;
837 werner 164
        }
286 werner 165
 
497 werner 166
        // reset clock...
167
        GlobalSettings::instance()->setCurrentYear(1); // reset clock
395 werner 168
        // initialization of trees, output on startup
286 werner 169
        mModel->beforeRun();
1157 werner 170
        GlobalSettings::instance()->executeJSFunction("onAfterCreate");
128 Werner 171
    } catch(const IException &e) {
575 werner 172
        QString error_msg = e.message();
128 Werner 173
        Helper::msg(error_msg);
837 werner 174
        mLastError = error_msg;
175
        mHasError = true;
128 Werner 176
        qDebug() << error_msg;
177
    }
590 werner 178
    emit bufferLogs(false);
179
 
362 werner 180
    qDebug() << "Model created.";
128 Werner 181
}
182
 
183
void ModelController::destroy()
184
{
185
    if (canDestroy()) {
1157 werner 186
        GlobalSettings::instance()->executeJSFunction("onBeforeDestroy");
891 werner 187
        Model *m = mModel;
128 Werner 188
        mModel = 0;
891 werner 189
        delete m;
162 werner 190
        GlobalSettings::instance()->setCurrentYear(0);
128 Werner 191
        qDebug() << "ModelController: Model destroyed.";
192
    }
193
}
222 werner 194
 
223 werner 195
void ModelController::runloop()
196
{
515 werner 197
    static QTime sLastTime = QTime::currentTime();
678 werner 198
#ifdef ILAND_GUI
776 werner 199
 //   QApplication::processEvents();
678 werner 200
#else
776 werner 201
 //   QCoreApplication::processEvents();
678 werner 202
#endif
223 werner 203
    if (mPaused)
204
        return;
225 werner 205
    bool doStop = false;
837 werner 206
    mHasError = false;
515 werner 207
    if (GlobalSettings::instance()->currentYear()<=1) {
208
        sLastTime = QTime::currentTime(); // reset clock at the beginning of the simulation
209
    }
225 werner 210
 
211
    if (!mCanceled && GlobalSettings::instance()->currentYear() < mYearsToRun) {
590 werner 212
        emit bufferLogs(true);
776 werner 213
 
837 werner 214
        mHasError = runYear(); // do the work!
776 werner 215
 
225 werner 216
        mRunning = true;
217
        emit year(GlobalSettings::instance()->currentYear());
837 werner 218
        if (!mHasError) {
515 werner 219
            int elapsed = sLastTime.msecsTo(QTime::currentTime());
497 werner 220
            int time=0;
515 werner 221
            if (currentYear()%50==0 && elapsed>10000)
497 werner 222
                time = 100; // a 100ms pause...
515 werner 223
            if (currentYear()%100==0 && elapsed>10000) {
497 werner 224
                time = 500; // a 500ms pause...
225
            }
515 werner 226
            if (time>0) {
227
                sLastTime = QTime::currentTime(); // reset clock
228
                qDebug() << "--- little break ---- (after " << elapsed << "ms).";
776 werner 229
                //QTimer::singleShot(time,this, SLOT(runloop()));
515 werner 230
            }
776 werner 231
 
837 werner 232
        } else {
233
           doStop = true; // an error occured
234
           mLastError = "An error occured while running the model. Please check the logs.";
235
           mHasError = true;
497 werner 236
        }
225 werner 237
 
238
    } else {
239
        doStop = true; // all years simulated
223 werner 240
    }
225 werner 241
 
242
    if (doStop || mCanceled) {
243
                // finished
776 werner 244
        internalStop();
225 werner 245
    }
267 werner 246
 
678 werner 247
#ifdef ILAND_GUI
225 werner 248
    QApplication::processEvents();
678 werner 249
#else
250
    QCoreApplication::processEvents();
251
#endif
223 werner 252
}
253
 
776 werner 254
bool ModelController::internalRun()
255
{
256
    // main loop
918 werner 257
    try {
258
        while (mRunning && !mPaused &&  !mFinished) {
259
            runloop(); // start the running loop
260
        }
261
    } catch (IException &e) {
262
#ifdef ILAND_GUI
263
        Helper::msg(e.message());
1034 werner 264
#else
265
        qDebug() << e.message();
918 werner 266
#endif
776 werner 267
 
268
    }
269
    return isFinished();
270
}
271
 
272
void ModelController::internalStop()
273
{
274
    if (mRunning) {
275
        GlobalSettings::instance()->outputManager()->save();
276
        DebugTimer::printAllTimers();
1182 werner 277
        saveDebugOutputs();
1081 werner 278
        //if (GlobalSettings::instance()->dbout().isOpen())
279
        //    GlobalSettings::instance()->dbout().close();
1072 werner 280
 
776 werner 281
        mFinished = true;
282
    }
283
    mRunning = false;
964 werner 284
    mPaused = false; // in any case
776 werner 285
    emit bufferLogs(false); // stop buffering
286
    emit finished(QString());
287
    emit stateChanged();
288
 
289
}
290
 
222 werner 291
void ModelController::run(int years)
128 Werner 292
{
222 werner 293
    if (!canRun())
294
        return;
590 werner 295
    emit bufferLogs(true); // start buffering
296
 
222 werner 297
    DebugTimer many_runs(QString("Timer for %1 runs").arg(years));
223 werner 298
    mPaused = false;
225 werner 299
    mFinished = false;
300
    mCanceled = false;
223 werner 301
    mYearsToRun = years;
497 werner 302
    //GlobalSettings::instance()->setCurrentYear(1); // reset clock
222 werner 303
 
304
    DebugTimer::clearAllTimers();
223 werner 305
 
759 werner 306
    mRunning = true;
776 werner 307
    emit stateChanged();
223 werner 308
 
776 werner 309
    qDebug() << "ModelControler: runloop started.";
310
    internalRun();
311
    emit stateChanged();
128 Werner 312
}
313
 
223 werner 314
bool ModelController::runYear()
128 Werner 315
{
223 werner 316
    if (!canRun()) return false;
421 werner 317
    DebugTimer t("ModelController:runYear");
1157 werner 318
    qDebug() << QDateTime::currentDateTime().toString("hh:mm:ss:") << "ModelController: run year" << currentYear();
421 werner 319
 
171 werner 320
    if (GlobalSettings::instance()->settings().paramValueBool("debug_clear"))
164 werner 321
        GlobalSettings::instance()->clearDebugLists();  // clear debug data
223 werner 322
    bool err=false;
128 Werner 323
    try {
590 werner 324
        emit bufferLogs(true);
1157 werner 325
        GlobalSettings::instance()->executeJSFunction("onYearBegin");
128 Werner 326
        mModel->runYear();
1157 werner 327
 
162 werner 328
        fetchDynamicOutput();
128 Werner 329
    } catch(const IException &e) {
575 werner 330
        QString error_msg = e.message();
128 Werner 331
        Helper::msg(error_msg);
332
        qDebug() << error_msg;
223 werner 333
        err=true;
128 Werner 334
    }
590 werner 335
    emit bufferLogs(false);
1157 werner 336
#ifdef ILAND_GUI
337
    QApplication::processEvents();
338
#else
339
    QCoreApplication::processEvents();
340
#endif
341
 
223 werner 342
    return err;
128 Werner 343
}
344
 
222 werner 345
bool ModelController::pause()
346
{
223 werner 347
    if(!isRunning())
348
        return mPaused;
349
 
350
    if (mPaused) {
351
        // currently in pause - mode -> continue
352
        mPaused = false;
776 werner 353
 
223 werner 354
    } else {
355
        // currently running -> set to pause mode
356
        GlobalSettings::instance()->outputManager()->save();
357
        mPaused = true;
590 werner 358
        emit bufferLogs(false);
223 werner 359
    }
776 werner 360
    emit stateChanged();
223 werner 361
    return mPaused;
222 werner 362
}
128 Werner 363
 
776 werner 364
bool ModelController::continueRun()
365
{
366
    mRunning = true;
367
    emit stateChanged();
368
    return internalRun();
369
}
370
 
222 werner 371
void ModelController::cancel()
372
{
225 werner 373
    mCanceled = true;
776 werner 374
    internalStop();
375
    emit stateChanged();
222 werner 376
}
225 werner 377
 
921 werner 378
 
379
// this function is called when exceptions occur in multithreaded code.
632 werner 380
QMutex error_mutex;
381
void ModelController::throwError(const QString msg)
382
{
383
    QMutexLocker lock(&error_mutex); // serialize access
384
    qDebug() << "ModelController: throwError reached:";
385
    qDebug() << msg;
837 werner 386
    mLastError = msg;
387
    mHasError = true;
632 werner 388
    emit bufferLogs(false);
389
    emit bufferLogs(true); // start buffering again
390
 
921 werner 391
    emit finished(msg);
632 werner 392
    throw IException(msg); // raise error again
393
 
394
}
161 werner 395
//////////////////////////////////////
396
// dynamic outut
397
//////////////////////////////////////
398
//////////////////////////////////////
399
void ModelController::setupDynamicOutput(QString fieldList)
400
{
171 werner 401
    mDynFieldList.clear();
402
    if (!fieldList.isEmpty()) {
403
        QRegExp rx("((?:\\[.+\\]|\\w+)\\.\\w+)");
404
        int pos=0;
405
        while ((pos = rx.indexIn(fieldList, pos)) != -1) {
406
            mDynFieldList.append(rx.cap(1));
407
            pos += rx.matchedLength();
408
        }
409
 
410
        //mDynFieldList = fieldList.split(QRegExp("(?:\\[.+\\]|\\w+)\\.\\w+"), QString::SkipEmptyParts);
170 werner 411
        mDynFieldList.prepend("count");
412
        mDynFieldList.prepend("year"); // fixed fields.
413
    }
161 werner 414
    mDynData.clear();
415
    mDynData.append(mDynFieldList.join(";"));
802 werner 416
    mDynamicOutputEnabled = true;
161 werner 417
}
162 werner 418
 
161 werner 419
QString ModelController::dynamicOutput()
420
{
421
    return mDynData.join("\n");
422
}
423
 
218 werner 424
const QStringList aggList = QStringList() << "mean" << "sum" << "min" << "max" << "p25" << "p50" << "p75" << "p5"<< "p10" << "p90" << "p95";
161 werner 425
void ModelController::fetchDynamicOutput()
426
{
802 werner 427
    if (!mDynamicOutputEnabled || mDynFieldList.isEmpty())
161 werner 428
        return;
165 werner 429
    DebugTimer t("dynamic output");
161 werner 430
    QStringList var;
431
    QString lastVar = "";
432
    QVector<double> data;
433
    AllTreeIterator at(mModel);
434
    TreeWrapper tw;
435
    int var_index;
436
    StatData stat;
437
    double value;
438
    QStringList line;
165 werner 439
    Expression custom_expr;
440
    bool simple_expression;
161 werner 441
    foreach (QString field, mDynFieldList) {
163 werner 442
        if (field=="count" || field=="year")
443
            continue;
166 werner 444
        if (field.count()>0 && field.at(0)=='[') {
445
            QRegExp rex("\\[(.+)\\]\\.(\\w+)");
446
            rex.indexIn(field);
447
            var = rex.capturedTexts();
448
            var.pop_front(); // drop first element (contains the full string)
165 werner 449
            simple_expression = false;
450
        } else {
451
            var = field.split(QRegExp("\\W+"), QString::SkipEmptyParts);
452
            simple_expression = true;
453
        }
161 werner 454
        if (var.count()!=2)
455
                throw IException(QString("Invalid variable name for dynamic output:") + field);
456
        if (var.first()!=lastVar) {
457
            // load new field
458
            data.clear();
168 werner 459
            at.reset(); var_index = 0;
165 werner 460
            if (simple_expression) {
461
                var_index = tw.variableIndex(var.first());
166 werner 462
                if (var_index<0)
165 werner 463
                    throw IException(QString("Invalid variable name for dynamic output:") + var.first());
166 werner 464
 
165 werner 465
            } else {
466
                custom_expr.setExpression(var.first());
467
                custom_expr.setModelObject(&tw);
161 werner 468
            }
469
            while (Tree *t = at.next()) {
470
                tw.setTree(t);
165 werner 471
                if (simple_expression)
472
                    value = tw.value(var_index);
473
                else
474
                    value = custom_expr.execute();
166 werner 475
                data.push_back(value);
161 werner 476
            }
477
            stat.setData(data);
478
        }
479
        // fetch data
480
        var_index = aggList.indexOf(var[1]);
481
        switch (var_index) {
482
            case 0: value = stat.mean(); break;
483
            case 1: value = stat.sum(); break;
484
            case 2: value = stat.min(); break;
485
            case 3: value = stat.max(); break;
486
            case 4: value = stat.percentile25(); break;
487
            case 5: value = stat.median(); break;
488
            case 6: value = stat.percentile75(); break;
218 werner 489
            case 7: value = stat.percentile(5); break;
490
            case 8: value = stat.percentile(10); break;
491
            case 9: value = stat.percentile(90); break;
492
            case 10: value = stat.percentile(95); break;
161 werner 493
            default: throw IException(QString("Invalid aggregate expression for dynamic output: %1\nallowed:%2")
494
                                  .arg(var[1]).arg(aggList.join(" ")));
495
        }
496
        line+=QString::number(value);
497
    }
162 werner 498
    line.prepend( QString::number(data.size()) );
499
    line.prepend( QString::number(GlobalSettings::instance()->currentYear()) );
500
    mDynData.append(line.join(";"));
161 werner 501
}
590 werner 502
 
1182 werner 503
void ModelController::saveDebugOutputs()
504
{
505
    // save to files if switch is true
506
    if (!GlobalSettings::instance()->settings().valueBool("system.settings.debugOutputAutoSave"))
507
        return;
508
 
509
    QString p = GlobalSettings::instance()->path("debug_", "temp");
510
 
511
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreePartition, ";", p + "tree_partition.csv");
512
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreeGrowth, ";", p + "tree_growth.csv");
513
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreeNPP, ";", p + "tree_npp.csv");
1196 werner 514
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dStandGPP, ";", p + "stand_gpp.csv");
1182 werner 515
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dWaterCycle, ";", p + "water_cycle.csv");
516
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dDailyResponses, ";", p + "daily_responses.csv");
517
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dEstablishment, ";", p + "establishment.csv");
518
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dSaplingGrowth, ";", p + "saplinggrowth.csv");
519
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dCarbonCycle, ";", p + "carboncycle.csv");
520
    GlobalSettings::instance()->debugDataTable(GlobalSettings::dPerformance, ";", p + "performance.csv");
521
    Helper::saveToTextFile(p+"dynamic.csv", dynamicOutput());
522
    Helper::saveToTextFile(p+ "version.txt", verboseVersion());
523
 
524
 
525
    qDebug() << "saved debug outputs to" << p;
526
 
527
}
528
 
590 werner 529
void ModelController::saveScreenshot(QString file_name)
530
{
678 werner 531
#ifdef ILAND_GUI
590 werner 532
    if (!mViewerWindow)
533
        return;
534
    QImage img = mViewerWindow->screenshot();
1180 werner 535
    img.save(GlobalSettings::instance()->path(file_name));
1032 werner 536
#else
537
    Q_UNUSED(file_name);
678 werner 538
#endif
590 werner 539
}
596 werner 540
 
541
void ModelController::paintMap(MapGrid *map, double min_value, double max_value)
542
{
678 werner 543
#ifdef ILAND_GUI
596 werner 544
    if (mViewerWindow) {
643 werner 545
        mViewerWindow->paintGrid(map, "", GridViewRainbow, min_value, max_value);
596 werner 546
        qDebug() << "painted map grid" << map->name() << "min-value (blue):" << min_value << "max-value(red):" << max_value;
547
    }
780 werner 548
#else
549
    Q_UNUSED(map);Q_UNUSED(min_value);Q_UNUSED(max_value);
678 werner 550
#endif
596 werner 551
}
632 werner 552
 
649 werner 553
void ModelController::addGrid(const FloatGrid *grid, const QString &name, const GridViewType view_type, double min_value, double max_value)
642 werner 554
{
678 werner 555
#ifdef ILAND_GUI
556
 
642 werner 557
    if (mViewerWindow) {
643 werner 558
        mViewerWindow->paintGrid(grid, name, view_type, min_value, max_value);
642 werner 559
        qDebug() << "painted grid min-value (blue):" << min_value << "max-value(red):" << max_value;
560
    }
780 werner 561
#else
562
    Q_UNUSED(grid); Q_UNUSED(name); Q_UNUSED(view_type); Q_UNUSED(min_value);Q_UNUSED(max_value);
678 werner 563
#endif
642 werner 564
}
565
 
649 werner 566
void ModelController::addLayers(const LayeredGridBase *layers, const QString &name)
634 werner 567
{
678 werner 568
#ifdef ILAND_GUI
634 werner 569
    if (mViewerWindow)
649 werner 570
        mViewerWindow->addLayers(layers, name);
571
    //qDebug() << layers->names();
780 werner 572
#else
573
    Q_UNUSED(layers); Q_UNUSED(name);
678 werner 574
#endif
634 werner 575
}
893 werner 576
void ModelController::removeLayers(const LayeredGridBase *layers)
577
{
578
#ifdef ILAND_GUI
579
    if (mViewerWindow)
580
        mViewerWindow->removeLayers(layers);
581
    //qDebug() << layers->names();
582
#else
924 werner 583
    Q_UNUSED(layers);
893 werner 584
#endif
585
}
632 werner 586
 
649 werner 587
void ModelController::setViewport(QPointF center_point, double scale_px_per_m)
646 werner 588
{
678 werner 589
#ifdef ILAND_GUI
647 werner 590
    if (mViewerWindow)
649 werner 591
        mViewerWindow->setViewport(center_point, scale_px_per_m);
780 werner 592
#else
593
    Q_UNUSED(center_point);Q_UNUSED(scale_px_per_m);
678 werner 594
#endif
646 werner 595
}
634 werner 596
 
1061 werner 597
void ModelController::setUIShortcuts(QVariantMap shortcuts)
598
{
599
#ifdef ILAND_GUI
600
    if (mViewerWindow)
601
        mViewerWindow->setUIshortcuts(shortcuts);
602
#else
603
    Q_UNUSED(shortcuts);
604
#endif
605
}
606
 
652 werner 607
void ModelController::repaint()
608
{
678 werner 609
#ifdef ILAND_GUI
652 werner 610
    if (mViewerWindow)
611
        mViewerWindow->repaint();
678 werner 612
#endif
652 werner 613
}
646 werner 614
 
649 werner 615
 
652 werner 616
 
962 werner 617
 
618