Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

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