Subversion Repositories tpanel

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 andreas 1
/*
386 andreas 2
 * Copyright (C) 2020 to 2023 by Andreas Theofilu <andreas@theosys.at>
5 andreas 3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software Foundation,
16
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
17
 */
18
 
296 andreas 19
#include <functional>
20
#include <map>
21
 
5 andreas 22
#include <QTextObject>
23
#include <QLabel>
24
#include <QImage>
25
#include <QWidget>
41 andreas 26
#include <QPropertyAnimation>
279 andreas 27
#include <QListWidget>
264 andreas 28
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
21 andreas 29
#include <QtMultimedia/QMediaPlayer>
30
#include <QtMultimediaWidgets/QVideoWidget>
187 andreas 31
#else
32
#include <QTextLayout>
181 andreas 33
#endif
5 andreas 34
#include "tobject.h"
35
#include "terror.h"
285 andreas 36
#include "tqscrollarea.h"
294 andreas 37
#include "tlock.h"
296 andreas 38
#include "tqtmain.h"
297 andreas 39
#include "tresources.h"
5 andreas 40
 
107 andreas 41
using std::string;
296 andreas 42
using std::map;
43
using std::pair;
107 andreas 44
 
5 andreas 45
TObject::TObject()
46
{
47
    DECL_TRACER("TObject::TObject()");
48
}
49
 
50
TObject::~TObject()
51
{
52
    DECL_TRACER("TObject::~TObject()");
53
 
89 andreas 54
    clear();
55
}
56
 
57
void TObject::clear(bool force)
58
{
59
    DECL_TRACER("TObject::clear()");
60
 
296 andreas 61
    if (mObjects.empty())
62
        return;
5 andreas 63
 
296 andreas 64
    std::map<ulong, OBJECT_t>::iterator iter;
65
 
66
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
5 andreas 67
    {
89 andreas 68
        if (!force)
296 andreas 69
            dropContent(&iter->second);
5 andreas 70
    }
89 andreas 71
 
296 andreas 72
    mObjects.clear();
5 andreas 73
}
74
 
198 andreas 75
void TObject::dropContent(OBJECT_t* obj, bool lock)
14 andreas 76
{
198 andreas 77
    DECL_TRACER("TObject::dropContent(OBJECT_t* obj, bool lock)");
14 andreas 78
 
349 andreas 79
    if (!obj)
80
        return;
81
 
198 andreas 82
    if (lock)
294 andreas 83
        TLOCKER(mutex_obj);
107 andreas 84
 
89 andreas 85
    try
14 andreas 86
    {
89 andreas 87
        switch (obj->type)
88
        {
89
            case OBJ_TEXT:
90
            case OBJ_INPUT:
188 andreas 91
                if (obj->object.plaintext)
190 andreas 92
                {
93
                    obj->object.plaintext->close();
188 andreas 94
                    obj->object.plaintext = nullptr;
190 andreas 95
                }
198 andreas 96
 
89 andreas 97
                obj->wid = 0;
271 andreas 98
                obj->invalid = true;
89 andreas 99
            break;
51 andreas 100
 
89 andreas 101
            case OBJ_BUTTON:
102
                if (obj->object.label)
103
                    obj->object.label = nullptr;
271 andreas 104
 
105
                obj->invalid = true;
89 andreas 106
            break;
107
 
185 andreas 108
            // This are the parent widgets (windows) and must be deleted.
109
            // If this widgets are deleted, Qt deletes their children.
89 andreas 110
            case OBJ_PAGE:
111
            case OBJ_SUBPAGE:
271 andreas 112
                obj->invalid = true;
113
 
89 andreas 114
                if (obj->object.widget)
115
                {
271 andreas 116
                    if (obj->type == OBJ_SUBPAGE)
117
                    {
264 andreas 118
                        obj->object.widget->close();        // This deletes all childs and the widget itself
271 andreas 119
                        obj->object.widget = nullptr;
120
                    }
121
                    else
122
                        obj->object.widget->hide();         // Don't delete a page because it is still stored in the stacked widget.
96 andreas 123
                }
198 andreas 124
            break;
14 andreas 125
 
279 andreas 126
            case OBJ_SUBVIEW:
127
                obj->invalid = true;
128
 
285 andreas 129
                if (obj->object.area)
279 andreas 130
                {
296 andreas 131
                    if (mMainWindow)
321 andreas 132
                    {
133
                        obj->connected = false;
296 andreas 134
                        mMainWindow->disconnectArea(obj->object.area);
321 andreas 135
                    }
296 andreas 136
 
285 andreas 137
                    delete obj->object.area;
138
                    obj->object.area = nullptr;
279 andreas 139
                }
140
            break;
141
 
89 andreas 142
            case OBJ_VIDEO:
143
                if (obj->object.vwidget)
144
                {
264 andreas 145
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
89 andreas 146
                    if (obj->player)
147
                        delete obj->player;
182 andreas 148
#endif
89 andreas 149
                    obj->object.vwidget = nullptr;
150
                    obj->player = nullptr;
151
                }
271 andreas 152
 
153
                obj->invalid = true;
200 andreas 154
            break;
21 andreas 155
 
200 andreas 156
            case OBJ_LIST:
157
                if (obj->object.list)
296 andreas 158
                {
159
                    if (mMainWindow)
160
                        mMainWindow->disconnectList(obj->object.list);
161
 
162
                    obj->object.list->close();
200 andreas 163
                    obj->object.list = nullptr;
296 andreas 164
                }
271 andreas 165
 
166
                obj->invalid = true;
200 andreas 167
            break;
168
 
89 andreas 169
            default:
271 andreas 170
                obj->invalid = true;
89 andreas 171
                break;
172
        }
14 andreas 173
    }
89 andreas 174
    catch (std::exception& e)
175
    {
176
        MSG_ERROR("Error freeing an object: " << e.what());
177
    }
14 andreas 178
}
179
 
386 andreas 180
void TObject::markDroped(ulong handle)
181
{
182
    DECL_TRACER("TObject::markDroped(ulong handle)");
183
 
184
    OBJECT_t *obj = findObject(handle);
185
 
186
    if (!obj)
187
        return;
188
 
189
    obj->remove = true;
190
}
191
 
296 andreas 192
bool TObject::addObject(OBJECT_t& obj)
5 andreas 193
{
296 andreas 194
    DECL_TRACER("TObject::addObject(OBJECT_t& obj)");
5 andreas 195
 
296 andreas 196
    if (obj.handle == 0 || obj.type == OBJ_NONE)
197
        return false;
5 andreas 198
 
349 andreas 199
    TLOCKER(mutex_obj);
200
 
201
    if (!mObjects.empty() && mObjects.find(obj.handle) != mObjects.end())
296 andreas 202
        return false;
5 andreas 203
 
296 andreas 204
    const auto [o, success] = mObjects.insert(pair<ulong, OBJECT_t>(obj.handle, obj));
205
    return success;
5 andreas 206
}
207
 
208
TObject::OBJECT_t *TObject::findObject(ulong handle)
209
{
210
    DECL_TRACER("TObject::findObject(ulong handle)");
211
 
349 andreas 212
    if (mObjects.empty())
213
        return nullptr;
214
 
296 andreas 215
    map<ulong, OBJECT_t>::iterator iter = mObjects.find(handle);
5 andreas 216
 
296 andreas 217
    if (iter != mObjects.end())
218
        return &iter->second;
5 andreas 219
 
220
    return nullptr;
221
}
222
 
349 andreas 223
TObject::OBJECT_t *TObject::findObject(WId id)
51 andreas 224
{
225
    DECL_TRACER("TObject::findObject(WId id)");
226
 
296 andreas 227
    if (mObjects.empty())
228
        return nullptr;
51 andreas 229
 
296 andreas 230
    map<ulong, OBJECT_t>::iterator iter;
231
 
232
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
51 andreas 233
    {
296 andreas 234
        if (iter->second.wid == id)
235
            return &iter->second;
51 andreas 236
    }
237
 
238
    return nullptr;
239
}
240
 
11 andreas 241
TObject::OBJECT_t *TObject::findFirstChild(ulong handle)
242
{
243
    DECL_TRACER("TObject::findFirstChild(ulong handle)");
244
 
349 andreas 245
    if (mObjects.empty())
246
        return nullptr;
247
 
296 andreas 248
    map<ulong, OBJECT_t>::iterator iter;
11 andreas 249
 
296 andreas 250
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
11 andreas 251
    {
296 andreas 252
        if (iter->first != (handle & 0xffff0000) && (iter->first & 0xffff0000) == (handle & 0xffff0000))
253
            return &iter->second;
11 andreas 254
    }
255
 
256
    return nullptr;
257
}
258
 
259
TObject::OBJECT_t *TObject::findNextChild(ulong handle)
260
{
261
    DECL_TRACER("TObject::findNextChild(ulong handle)");
262
 
349 andreas 263
    if (mObjects.empty())
264
        return nullptr;
265
 
296 andreas 266
    map<ulong, OBJECT_t>::iterator iter;
267
    bool next = true;
11 andreas 268
 
296 andreas 269
    for (iter = mObjects.find(handle); iter != mObjects.end(); ++iter)
11 andreas 270
    {
296 andreas 271
        if (next)
272
        {
273
            next = false;
274
            continue;
275
        }
11 andreas 276
 
296 andreas 277
        if ((iter->first & 0xffff0000) == (handle & 0xffff0000))
278
            return &iter->second;
11 andreas 279
    }
280
 
281
    return nullptr;
282
}
283
 
42 andreas 284
TObject::OBJECT_t * TObject::getMarkedRemove()
285
{
286
    DECL_TRACER("TObject::getMarkedRemove()");
51 andreas 287
 
349 andreas 288
    if (mObjects.empty())
289
        return nullptr;
290
 
296 andreas 291
    map<ulong, OBJECT_t>::iterator iter;
51 andreas 292
 
296 andreas 293
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
42 andreas 294
    {
296 andreas 295
        if (iter->second.remove)
296
            return &iter->second;
42 andreas 297
    }
51 andreas 298
 
42 andreas 299
    return nullptr;
300
}
301
 
302
TObject::OBJECT_t * TObject::getNextMarkedRemove(TObject::OBJECT_t* object)
303
{
304
    DECL_TRACER("TObject::getNextMarkedRemove(TObject::OBJECT_t* obj)");
51 andreas 305
 
349 andreas 306
    if (!object || mObjects.empty())
42 andreas 307
        return nullptr;
51 andreas 308
 
296 andreas 309
    map<ulong, OBJECT_t>::iterator iter;
310
    bool next = true;
51 andreas 311
 
296 andreas 312
    for (iter = mObjects.find(object->handle); iter != mObjects.end(); ++iter)
42 andreas 313
    {
296 andreas 314
        if (next)
315
        {
316
            next = false;
317
            continue;
318
        }
51 andreas 319
 
296 andreas 320
        if (iter->second.remove)
321
            return &iter->second;
42 andreas 322
    }
51 andreas 323
 
42 andreas 324
    return nullptr;
325
}
326
 
383 andreas 327
TObject::OBJECT_t *TObject::getFirstDirty()
328
{
329
    DECL_TRACER("TObject::getFirstDirty()");
330
 
331
    if (mObjects.empty())
332
        return nullptr;
333
 
334
    map<ulong, OBJECT_t>::iterator iter;
335
 
336
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
337
    {
338
        if (iter->second.dirty)
339
            return &iter->second;
340
    }
341
 
342
    return nullptr;
343
}
344
 
345
TObject::OBJECT_t *TObject::getNextDirty(OBJECT_t *obj)
346
{
347
    DECL_TRACER("TObject::getNextDirty(OBJECT_t *obj)");
348
 
349
    if (!obj || mObjects.empty())
350
        return nullptr;
351
 
352
    map<ulong, OBJECT_t>::iterator iter;
353
    bool next = true;
354
 
355
    for (iter = mObjects.find(obj->handle); iter != mObjects.end(); ++iter)
356
    {
357
        if (next)
358
        {
359
            next = false;
360
            continue;
361
        }
362
 
363
        if (iter->second.dirty)
364
            return &iter->second;
365
    }
366
 
367
    return nullptr;
368
}
142 andreas 369
TObject::OBJECT_t *TObject::findFirstWindow()
370
{
371
    DECL_TRACER("TObject::getFirstWindow()");
372
 
349 andreas 373
    if (mObjects.empty())
374
        return nullptr;
375
 
296 andreas 376
    map<ulong, OBJECT_t>::iterator iter;
142 andreas 377
 
296 andreas 378
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
142 andreas 379
    {
296 andreas 380
        if (iter->second.type == OBJ_SUBPAGE)
381
            return &iter->second;
142 andreas 382
    }
383
 
384
    return nullptr;
385
}
386
 
387
TObject::OBJECT_t *TObject::findNextWindow(TObject::OBJECT_t *obj)
388
{
389
    DECL_TRACER("TObject::findNextWindow()");
390
 
349 andreas 391
    if (!obj || mObjects.empty())
142 andreas 392
        return nullptr;
393
 
296 andreas 394
    map<ulong, OBJECT_t>::iterator iter;
395
    bool next = true;
142 andreas 396
 
296 andreas 397
    for (iter = mObjects.find(obj->handle); iter != mObjects.end(); ++iter)
142 andreas 398
    {
296 andreas 399
        if (next)
400
        {
401
            next = false;
402
            continue;
403
        }
142 andreas 404
 
296 andreas 405
        if (iter->second.type == OBJ_SUBPAGE)
406
            return &iter->second;
142 andreas 407
    }
408
 
409
    return nullptr;
410
}
411
 
198 andreas 412
void TObject::removeObject(ulong handle, bool drop)
5 andreas 413
{
198 andreas 414
    DECL_TRACER("TObject::removeObject(ulong handle, bool drop)");
5 andreas 415
 
296 andreas 416
    if (!handle || mObjects.empty())
215 andreas 417
        return;
418
 
296 andreas 419
    TLOCKER(mutex_obj);
420
    map<ulong, OBJECT_t>::iterator iter = mObjects.find(handle);
5 andreas 421
 
296 andreas 422
    if (iter != mObjects.end())
5 andreas 423
    {
296 andreas 424
        if (drop)
425
            dropContent(&iter->second, false);
198 andreas 426
 
296 andreas 427
        mObjects.erase(iter);
5 andreas 428
    }
429
}
430
 
198 andreas 431
void TObject::removeAllChilds(ulong handle, bool drop)
11 andreas 432
{
198 andreas 433
    DECL_TRACER("TObject::removeAllChilds(ulong handle, bool drop)");
11 andreas 434
 
296 andreas 435
    if (!handle || mObjects.empty())
215 andreas 436
        return;
437
 
294 andreas 438
    TLOCKER(mutex_obj);
296 andreas 439
    map<ulong, OBJECT_t>::iterator iter;
440
    bool repeat = false;
11 andreas 441
 
296 andreas 442
    do
11 andreas 443
    {
296 andreas 444
        repeat = false;
445
 
446
        for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
14 andreas 447
        {
296 andreas 448
            if ((iter->first & 0xffff0000) == (handle & 0xffff0000) && iter->first != (handle & 0xffff0000))
14 andreas 449
            {
198 andreas 450
                if (drop)
296 andreas 451
                    dropContent(&iter->second, false);
198 andreas 452
 
296 andreas 453
                mObjects.erase(iter);
454
                repeat = true;
455
                break;
14 andreas 456
            }
457
        }
11 andreas 458
    }
296 andreas 459
    while (repeat);
11 andreas 460
}
14 andreas 461
 
107 andreas 462
void TObject::cleanMarked()
14 andreas 463
{
107 andreas 464
    DECL_TRACER("TObject::cleanMarked()");
465
 
349 andreas 466
    if (mObjects.empty())
467
        return;
468
 
296 andreas 469
    TLOCKER(mutex_obj);
470
    map<ulong, OBJECT_t>::iterator iter;
471
    bool repeat = false;
107 andreas 472
 
296 andreas 473
    do
107 andreas 474
    {
296 andreas 475
        repeat = false;
476
 
477
        for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
107 andreas 478
        {
296 andreas 479
            if (iter->second.remove && (!iter->second.animation || iter->second.animation->state() != QAbstractAnimation::Running))
198 andreas 480
            {
296 andreas 481
                if (iter->second.type == OBJ_SUBPAGE && iter->second.object.widget)
482
                {
483
                    iter->second.object.widget->close();
484
                    iter->second.object.widget = nullptr;
485
                }
107 andreas 486
 
296 andreas 487
                mObjects.erase(iter);
488
                repeat = true;
489
                break;
107 andreas 490
            }
491
        }
492
    }
296 andreas 493
    while (repeat);
107 andreas 494
}
495
 
217 andreas 496
void TObject::invalidateAllObjects()
497
{
498
    DECL_TRACER("TObject::invalidateAllObjects()");
499
 
349 andreas 500
    if (mObjects.empty())
501
        return;
502
 
296 andreas 503
    TLOCKER(mutex_obj);
504
    map<ulong, OBJECT_t>::iterator iter;
217 andreas 505
 
296 andreas 506
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
217 andreas 507
    {
296 andreas 508
        iter->second.remove = false;
509
        iter->second.invalid = true;
510
    }
511
}
217 andreas 512
 
296 andreas 513
void TObject::invalidateObject(ulong handle)
514
{
515
    DECL_TRACER("TObject::invalidateObject(ulong handle)");
516
 
349 andreas 517
    if (mObjects.empty())
518
        return;
519
 
296 andreas 520
    TLOCKER(mutex_obj);
521
    map<ulong, OBJECT_t>::iterator iter = mObjects.find(handle);
522
 
523
    if (iter != mObjects.end())
524
    {
525
        iter->second.remove = false;
526
        iter->second.invalid = true;
217 andreas 527
    }
528
}
529
 
530
void TObject::invalidateAllSubObjects(ulong handle)
531
{
297 andreas 532
    DECL_TRACER("TObject::invalidateAllSubObjects(ulong handle)");
217 andreas 533
 
297 andreas 534
    if (mObjects.empty())
535
        return;
536
 
350 andreas 537
    TLOCKER(mutex_obj);
538
 
296 andreas 539
    map<ulong, OBJECT_t>::iterator iter;
297 andreas 540
    bool first = true;
217 andreas 541
 
297 andreas 542
    for (iter = mObjects.find(handle); iter != mObjects.end(); ++iter)
217 andreas 543
    {
297 andreas 544
        if (first)
217 andreas 545
        {
297 andreas 546
            first = false;
547
            continue;
548
        }
549
 
550
        if ((iter->first & 0xffff0000) == handle)
551
        {
552
            MSG_DEBUG("Invalidating object " << handleToString(iter->first) << " of type " << objectToString(iter->second.type));
296 andreas 553
            iter->second.remove = false;
554
            iter->second.invalid = true;
297 andreas 555
 
321 andreas 556
            if (iter->second.type == OBJ_SUBVIEW && iter->second.object.area && mMainWindow && iter->second.connected)
557
            {
558
                iter->second.connected = false;
297 andreas 559
                mMainWindow->disconnectArea(iter->second.object.area);
321 andreas 560
            }
561
            else if (iter->second.type == OBJ_LIST && iter->second.object.list && mMainWindow && iter->second.connected)
562
            {
563
                iter->second.connected = false;
297 andreas 564
                mMainWindow->disconnectList(iter->second.object.list);
321 andreas 565
            }
217 andreas 566
        }
296 andreas 567
    }
568
}
217 andreas 569
 
296 andreas 570
bool TObject::enableObject(ulong handle)
571
{
572
    DECL_TRACER("TObject::enableObject(ulong handle)");
573
 
349 andreas 574
    if (mObjects.empty())
575
        return false;
576
 
296 andreas 577
    map<ulong, OBJECT_t>::iterator iter = mObjects.find(handle);
578
 
579
    if (iter != mObjects.end())
580
    {
581
        if (!iter->second.object.widget)
582
        {
583
            iter->second.remove = true;
297 andreas 584
            MSG_ERROR("Object " << handleToString(iter->first) << " of type " << objectToString(iter->second.type) << " has no QObject!");
296 andreas 585
            return false;
586
        }
587
 
323 andreas 588
        TLOCKER(mutex_obj);
296 andreas 589
        iter->second.remove = false;
590
        iter->second.invalid = false;
591
 
321 andreas 592
        if (iter->second.type == OBJ_SUBVIEW && iter->second.object.area && mMainWindow && !iter->second.connected)
593
        {
297 andreas 594
            mMainWindow->reconnectArea(iter->second.object.area);
321 andreas 595
            iter->second.connected = true;
596
        }
597
        else if (iter->second.type == OBJ_LIST && iter->second.object.list && mMainWindow && !iter->second.connected)
598
        {
297 andreas 599
            mMainWindow->reconnectList(iter->second.object.list);
321 andreas 600
            iter->second.connected = true;
601
        }
297 andreas 602
 
296 andreas 603
        return true;                         // We can savely return here because a handle is unique
217 andreas 604
    }
605
 
296 andreas 606
    return false;
217 andreas 607
}
608
 
296 andreas 609
bool TObject::enableAllSubObjects(ulong handle)
610
{
611
    DECL_TRACER("::enableAllSubObjects(ulong handle)");
612
 
349 andreas 613
    if (mObjects.empty())
614
        return false;
615
 
296 andreas 616
    TLOCKER(mutex_obj);
617
    map<ulong, OBJECT_t>::iterator iter;
618
    bool ret = true;
619
 
620
    for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
621
    {
622
        if (iter->first != handle && (iter->first & 0xffff0000) == handle)
623
        {
624
            if (!iter->second.object.widget)
625
            {
626
                iter->second.remove = true;
297 andreas 627
                MSG_ERROR("Object " << handleToString(iter->first) << " of type " << objectToString(iter->second.type) << " has no QObject!");
296 andreas 628
                ret = false;
629
            }
630
            else
631
            {
632
                iter->second.remove = false;
633
                iter->second.invalid = false;
297 andreas 634
 
321 andreas 635
                if (iter->second.type == OBJ_SUBVIEW && iter->second.object.area && mMainWindow && !iter->second.connected)
636
                {
297 andreas 637
                    mMainWindow->reconnectArea(iter->second.object.area);
321 andreas 638
                    iter->second.connected = true;
639
                }
640
                else if (iter->second.type == OBJ_LIST && iter->second.object.list && mMainWindow && !iter->second.connected)
641
                {
297 andreas 642
                    mMainWindow->reconnectList(iter->second.object.list);
321 andreas 643
                    iter->second.connected = true;
644
                }
296 andreas 645
            }
646
        }
647
    }
648
 
649
    return ret;
650
}
651
 
107 andreas 652
string TObject::objectToString(TObject::OBJECT_TYPE o)
653
{
14 andreas 654
    switch(o)
655
    {
656
        case OBJ_BUTTON:  return "BUTTON"; break;
657
        case OBJ_INPUT:   return "INPUT"; break;
658
        case OBJ_NONE:    return "undefined"; break;
659
        case OBJ_PAGE:    return "PAGE"; break;
660
        case OBJ_SUBPAGE: return "SUBPAGE"; break;
661
        case OBJ_TEXT:    return "TEXT"; break;
21 andreas 662
        case OBJ_VIDEO:   return "VIDEO"; break;
200 andreas 663
        case OBJ_LIST:    return "LIST"; break;
279 andreas 664
        case OBJ_SUBVIEW: return "SUBVIEW"; break;
14 andreas 665
    }
78 andreas 666
 
107 andreas 667
    return string();   // Should not happen but is needed to satisfy the compiler.
14 andreas 668
}