Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/embeddedobj/source/msole/ownview.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <com/sun/star/frame/Desktop.hpp>
21
#include <com/sun/star/frame/XFrame.hpp>
22
#include <com/sun/star/frame/XController.hpp>
23
#include <com/sun/star/awt/XTopWindow.hpp>
24
#include <com/sun/star/embed/XClassifiedObject.hpp>
25
#include <com/sun/star/io/TempFile.hpp>
26
#include <com/sun/star/io/XStream.hpp>
27
#include <com/sun/star/io/XOutputStream.hpp>
28
#include <com/sun/star/io/XSeekable.hpp>
29
#include <com/sun/star/task/XInteractionHandler.hpp>
30
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
31
#include <com/sun/star/util/XCloseable.hpp>
32
33
#include <com/sun/star/document/XEventBroadcaster.hpp>
34
#include <com/sun/star/document/XTypeDetection.hpp>
35
#include <com/sun/star/container/XNameAccess.hpp>
36
#include <cppuhelper/implbase.hxx>
37
#include <comphelper/storagehelper.hxx>
38
#include <comphelper/mimeconfighelper.hxx>
39
#include <comphelper/diagnose_ex.hxx>
40
41
#include "olepersist.hxx"
42
#include "ownview.hxx"
43
44
45
using namespace ::com::sun::star;
46
using namespace ::comphelper;
47
48
namespace {
49
50
class DummyHandler_Impl : public ::cppu::WeakImplHelper< task::XInteractionHandler >
51
{
52
public:
53
0
    DummyHandler_Impl() {}
54
55
    virtual void SAL_CALL handle( const uno::Reference< task::XInteractionRequest >& xRequest ) override;
56
};
57
58
}
59
60
void SAL_CALL DummyHandler_Impl::handle( const uno::Reference< task::XInteractionRequest >& )
61
0
{
62
0
}
63
64
65
// Object viewer
66
67
68
OwnView_Impl::OwnView_Impl( const uno::Reference< uno::XComponentContext >& xContext,
69
                            const uno::Reference< io::XInputStream >& xInputStream )
70
0
: m_xContext( xContext )
71
0
, m_bBusy( false )
72
0
, m_bUseNative( false )
73
0
{
74
0
    if ( !xContext.is() || !xInputStream.is() )
75
0
        throw uno::RuntimeException();
76
77
0
    m_aTempFileURL = GetNewFilledTempFile_Impl( xInputStream, m_xContext );
78
0
}
79
80
81
OwnView_Impl::~OwnView_Impl()
82
0
{
83
0
    try {
84
0
        KillFile_Impl( m_aTempFileURL, m_xContext );
85
0
    } catch( uno::Exception& ) {}
86
87
0
    try {
88
0
        if ( !m_aNativeTempURL.isEmpty() )
89
0
            KillFile_Impl( m_aNativeTempURL, m_xContext );
90
0
    } catch( uno::Exception& ) {}
91
0
}
92
93
94
bool OwnView_Impl::CreateModelFromURL( const OUString& aFileURL )
95
0
{
96
0
    bool bResult = false;
97
98
0
    if ( !aFileURL.isEmpty() )
99
0
    {
100
0
        try {
101
0
            uno::Reference < frame::XDesktop2 > xDocumentLoader = frame::Desktop::create(m_xContext);
102
103
0
            uno::Sequence< beans::PropertyValue > aArgs( m_aFilterName.isEmpty() ? 4 : 5 );
104
0
            auto pArgs = aArgs.getArray();
105
106
0
            pArgs[0].Name = "URL";
107
0
            pArgs[0].Value <<= aFileURL;
108
109
0
            pArgs[1].Name = "ReadOnly";
110
0
            pArgs[1].Value <<= true;
111
112
0
            pArgs[2].Name = "InteractionHandler";
113
0
            pArgs[2].Value <<= uno::Reference< task::XInteractionHandler >( new DummyHandler_Impl() );
114
115
0
            pArgs[3].Name = "DontEdit";
116
0
            pArgs[3].Value <<= true;
117
118
0
            if ( !m_aFilterName.isEmpty() )
119
0
            {
120
0
                pArgs[4].Name = "FilterName";
121
0
                pArgs[4].Value <<= m_aFilterName;
122
0
            }
123
124
0
            uno::Reference< frame::XModel > xModel( xDocumentLoader->loadComponentFromURL(
125
0
                                                            aFileURL,
126
0
                                                            u"_blank"_ustr,
127
0
                                                            0,
128
0
                                                            aArgs ),
129
0
                                                        uno::UNO_QUERY );
130
131
0
            if ( xModel.is() )
132
0
            {
133
0
                uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY );
134
0
                if ( xBroadCaster.is() )
135
0
                    xBroadCaster->addEventListener( uno::Reference< document::XEventListener >(this) );
136
137
0
                uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY );
138
0
                if ( xCloseable.is() )
139
0
                {
140
0
                    xCloseable->addCloseListener( uno::Reference< util::XCloseListener >(this) );
141
142
0
                    ::osl::MutexGuard aGuard( m_aMutex );
143
0
                    m_xModel = std::move(xModel);
144
0
                    bResult = true;
145
0
                }
146
0
            }
147
0
        }
148
0
        catch (uno::Exception const&)
149
0
        {
150
0
            TOOLS_WARN_EXCEPTION("embeddedobj.ole", "OwnView_Impl::CreateModelFromURL:");
151
0
        }
152
0
    }
153
154
0
    return bResult;
155
0
}
156
157
158
bool OwnView_Impl::CreateModel( bool bUseNative )
159
0
{
160
0
    bool bResult = false;
161
162
0
    try {
163
0
        bResult = CreateModelFromURL( bUseNative ? m_aNativeTempURL : m_aTempFileURL );
164
0
    }
165
0
    catch( uno::Exception& )
166
0
    {
167
0
    }
168
169
0
    return bResult;
170
0
}
171
172
173
OUString OwnView_Impl::GetFilterNameFromExtentionAndInStream(
174
                                                    const css::uno::Reference< css::uno::XComponentContext >& xContext,
175
                                                    std::u16string_view aNameWithExtention,
176
                                                    const uno::Reference< io::XInputStream >& xInputStream )
177
0
{
178
0
    if ( !xInputStream.is() )
179
0
        throw uno::RuntimeException();
180
181
0
    uno::Reference< document::XTypeDetection > xTypeDetection(
182
0
            xContext->getServiceManager()->createInstanceWithContext(u"com.sun.star.document.TypeDetection"_ustr, xContext),
183
0
            uno::UNO_QUERY_THROW );
184
185
0
    OUString aTypeName;
186
187
0
    if ( !aNameWithExtention.empty() )
188
0
    {
189
0
        OUString aURLToAnalyze = OUString::Concat("file:///") + aNameWithExtention;
190
0
        aTypeName = xTypeDetection->queryTypeByURL( aURLToAnalyze );
191
0
    }
192
193
0
    uno::Sequence< beans::PropertyValue > aArgs( aTypeName.isEmpty() ? 2 : 3 );
194
0
    auto pArgs = aArgs.getArray();
195
0
    pArgs[0].Name = "URL";
196
0
    pArgs[0].Value <<= u"private:stream"_ustr;
197
0
    pArgs[1].Name = "InputStream";
198
0
    pArgs[1].Value <<= xInputStream;
199
0
    if ( !aTypeName.isEmpty() )
200
0
    {
201
0
        pArgs[2].Name = "TypeName";
202
0
        pArgs[2].Value <<= aTypeName;
203
0
    }
204
205
0
    aTypeName = xTypeDetection->queryTypeByDescriptor( aArgs, true );
206
207
0
    OUString aFilterName;
208
0
    for (beans::PropertyValue const& prop : aArgs)
209
0
        if ( prop.Name == "FilterName" )
210
0
            prop.Value >>= aFilterName;
211
212
0
    if ( aFilterName.isEmpty() && !aTypeName.isEmpty() )
213
0
    {
214
        // get the default filter name for the type
215
0
        uno::Reference< container::XNameAccess > xNameAccess( xTypeDetection, uno::UNO_QUERY_THROW );
216
0
        uno::Sequence< beans::PropertyValue > aTypes;
217
218
0
        if ( xNameAccess.is() && ( xNameAccess->getByName( aTypeName ) >>= aTypes ) )
219
0
        {
220
0
            for (beans::PropertyValue const& prop : aTypes)
221
0
            {
222
0
                if ( prop.Name == "PreferredFilter" && ( prop.Value >>= aFilterName ) )
223
0
                {
224
0
                    prop.Value >>= aFilterName;
225
0
                    break;
226
0
                }
227
0
            }
228
0
        }
229
0
    }
230
231
0
    return aFilterName;
232
0
}
233
234
235
bool OwnView_Impl::ReadContentsAndGenerateTempFile( const uno::Reference< io::XInputStream >& xInStream,
236
                                                        bool bParseHeader )
237
0
{
238
0
    uno::Reference< io::XSeekable > xSeekable( xInStream, uno::UNO_QUERY_THROW );
239
0
    xSeekable->seek( 0 );
240
241
    // create m_aNativeTempURL
242
0
    OUString aNativeTempURL;
243
0
    uno::Reference < io::XTempFile > xNativeTempFile(
244
0
            io::TempFile::create(m_xContext),
245
0
            uno::UNO_SET_THROW );
246
0
    uno::Reference < io::XOutputStream > xNativeOutTemp = xNativeTempFile->getOutputStream();
247
0
    uno::Reference < io::XInputStream > xNativeInTemp = xNativeTempFile->getInputStream();
248
0
    if ( !xNativeOutTemp.is() || !xNativeInTemp.is() )
249
0
        throw uno::RuntimeException();
250
251
0
    try {
252
0
        xNativeTempFile->setRemoveFile( false );
253
0
        aNativeTempURL = xNativeTempFile->getUri();
254
0
    }
255
0
    catch ( uno::Exception& )
256
0
    {
257
0
    }
258
259
0
    bool bFailed = false;
260
0
    OUString aFileSuffix;
261
262
0
    if ( bParseHeader )
263
0
    {
264
0
        uno::Sequence< sal_Int8 > aReadSeq( 4 );
265
        // read the complete size of the Object Package
266
0
        if ( xInStream->readBytes( aReadSeq, 4 ) != 4 )
267
0
            return false;
268
        // read the first header ( have no idea what does this header mean )
269
0
        if ( xInStream->readBytes( aReadSeq, 2 ) != 2 || aReadSeq[0] != 2 || aReadSeq[1] != 0 )
270
0
            return false;
271
272
        // read file name
273
        // only extension is interesting so only subset of symbols is accepted
274
0
        do
275
0
        {
276
0
            if ( xInStream->readBytes( aReadSeq, 1 ) != 1 )
277
0
                return false;
278
279
0
            if (
280
0
                (aReadSeq[0] >= '0' && aReadSeq[0] <= '9') ||
281
0
                (aReadSeq[0] >= 'a' && aReadSeq[0] <= 'z') ||
282
0
                (aReadSeq[0] >= 'A' && aReadSeq[0] <= 'Z') ||
283
0
                aReadSeq[0] == '.'
284
0
               )
285
0
            {
286
0
                aFileSuffix += OUStringChar( sal_Unicode(aReadSeq[0]) );
287
0
            }
288
289
0
        } while( aReadSeq[0] );
290
291
        // skip url
292
0
        do
293
0
        {
294
0
            if ( xInStream->readBytes( aReadSeq, 1 ) != 1 )
295
0
                return false;
296
0
        } while( aReadSeq[0] );
297
298
        // check the next header
299
0
        if ( xInStream->readBytes( aReadSeq, 4 ) != 4
300
0
          || aReadSeq[0] || aReadSeq[1] || aReadSeq[2] != 3 || aReadSeq[3] )
301
0
            return false;
302
303
        // get the size of the next entry
304
0
        if ( xInStream->readBytes( aReadSeq, 4 ) != 4 )
305
0
            return false;
306
307
0
        sal_uInt32 nUrlSize = static_cast<sal_uInt8>(aReadSeq[0])
308
0
                            + static_cast<sal_uInt8>(aReadSeq[1]) * 0x100
309
0
                            + static_cast<sal_uInt8>(aReadSeq[2]) * 0x10000
310
0
                            + static_cast<sal_uInt8>(aReadSeq[3]) * 0x1000000;
311
0
        sal_Int64 nTargetPos = xSeekable->getPosition() + nUrlSize;
312
313
0
        xSeekable->seek( nTargetPos );
314
315
        // get the size of stored data
316
0
        if ( xInStream->readBytes( aReadSeq, 4 ) != 4 )
317
0
            return false;
318
319
0
        sal_uInt32 nDataSize = static_cast<sal_uInt8>(aReadSeq[0])
320
0
                            + static_cast<sal_uInt8>(aReadSeq[1]) * 0x100
321
0
                            + static_cast<sal_uInt8>(aReadSeq[2]) * 0x10000
322
0
                            + static_cast<sal_uInt8>(aReadSeq[3]) * 0x1000000;
323
324
0
        aReadSeq.realloc( 32000 );
325
0
        sal_uInt32 nRead = 0;
326
0
        while ( nRead < nDataSize )
327
0
        {
328
0
            sal_uInt32 nToRead = std::min<sal_uInt32>( nDataSize - nRead, 32000 );
329
0
            sal_uInt32 nLocalRead = xInStream->readBytes( aReadSeq, nToRead );
330
331
332
0
            if ( !nLocalRead )
333
0
            {
334
0
                bFailed = true;
335
0
                break;
336
0
            }
337
0
            else if ( nLocalRead == 32000 )
338
0
                xNativeOutTemp->writeBytes( aReadSeq );
339
0
            else
340
0
            {
341
0
                uno::Sequence< sal_Int8 > aToWrite( aReadSeq );
342
0
                aToWrite.realloc( nLocalRead );
343
0
                xNativeOutTemp->writeBytes( aToWrite );
344
0
            }
345
346
0
            nRead += nLocalRead;
347
0
        }
348
0
    }
349
0
    else
350
0
    {
351
0
        uno::Sequence< sal_Int8 > aData( 8 );
352
0
        if ( xInStream->readBytes( aData, 8 ) == 8
353
0
          && aData[0] == -1 && aData[1] == -1 && aData[2] == -1 && aData[3] == -1
354
0
          && ( aData[4] == 2 || aData[4] == 3 ) && aData[5] == 0 && aData[6] == 0 && aData[7] == 0 )
355
0
        {
356
            // the header has to be removed
357
0
            xSeekable->seek( 40 );
358
0
        }
359
0
        else
360
0
        {
361
            // the usual Ole10Native format
362
0
            xSeekable->seek( 4 );
363
0
        }
364
365
0
        ::comphelper::OStorageHelper::CopyInputToOutput( xInStream, xNativeOutTemp );
366
0
    }
367
368
0
    xNativeOutTemp->closeOutput();
369
370
    // The temporary native file is created, now the filter must be detected
371
0
    if ( !bFailed )
372
0
    {
373
0
        m_aFilterName = GetFilterNameFromExtentionAndInStream( m_xContext, aFileSuffix, xNativeInTemp );
374
0
        m_aNativeTempURL = aNativeTempURL;
375
0
    }
376
377
0
    return !bFailed;
378
0
}
379
380
381
void OwnView_Impl::CreateNative()
382
0
{
383
0
    if ( !m_aNativeTempURL.isEmpty() )
384
0
        return;
385
386
0
    try
387
0
    {
388
0
        uno::Reference < ucb::XSimpleFileAccess3 > xAccess(
389
0
                ucb::SimpleFileAccess::create( m_xContext ) );
390
391
0
        uno::Reference< io::XInputStream > xInStream = xAccess->openFileRead( m_aTempFileURL );
392
0
        if ( !xInStream.is() )
393
0
            throw uno::RuntimeException();
394
395
0
        uno::Sequence< uno::Any > aArgs{ uno::Any(xInStream) };
396
0
        uno::Reference< container::XNameAccess > xNameAccess(
397
0
                m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
398
0
                        u"com.sun.star.embed.OLESimpleStorage"_ustr,
399
0
                        aArgs, m_xContext ),
400
0
                uno::UNO_QUERY_THROW );
401
402
0
        static constexpr OUString aSubStreamName(u"\1Ole10Native"_ustr);
403
0
        uno::Reference< embed::XClassifiedObject > xStor( xNameAccess, uno::UNO_QUERY_THROW );
404
0
        uno::Sequence< sal_Int8 > aStorClassID = xStor->getClassID();
405
406
0
        if ( xNameAccess->hasByName( aSubStreamName ) )
407
0
        {
408
0
            sal_uInt8 const aClassID[] =
409
0
                { 0x00, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
410
0
            uno::Sequence< sal_Int8 > aPackageClassID( reinterpret_cast<sal_Int8 const *>(aClassID), 16 );
411
412
0
            uno::Reference< io::XStream > xSubStream;
413
0
            xNameAccess->getByName( aSubStreamName ) >>= xSubStream;
414
0
            if ( xSubStream.is() )
415
0
            {
416
0
                bool bOk = false;
417
418
0
                if ( MimeConfigurationHelper::ClassIDsEqual( aPackageClassID, aStorClassID ) )
419
0
                {
420
                    // the storage represents Object Package
421
422
0
                    bOk = ReadContentsAndGenerateTempFile( xSubStream->getInputStream(), true );
423
424
0
                    if ( !bOk && !m_aNativeTempURL.isEmpty() )
425
0
                    {
426
0
                        KillFile_Impl( m_aNativeTempURL, m_xContext );
427
0
                        m_aNativeTempURL.clear();
428
0
                    }
429
0
                }
430
431
0
                if ( !bOk )
432
0
                {
433
0
                    bOk = ReadContentsAndGenerateTempFile( xSubStream->getInputStream(), false );
434
435
0
                    if ( !bOk && !m_aNativeTempURL.isEmpty() )
436
0
                    {
437
0
                        KillFile_Impl( m_aNativeTempURL, m_xContext );
438
0
                        m_aNativeTempURL.clear();
439
0
                    }
440
0
                }
441
0
            }
442
0
        }
443
0
        else
444
0
        {
445
            // TODO/LATER: No native stream, needs a new solution
446
0
        }
447
0
    }
448
0
    catch( uno::Exception& )
449
0
    {}
450
0
}
451
452
453
bool OwnView_Impl::Open()
454
0
{
455
0
    bool bResult = false;
456
457
0
    uno::Reference< frame::XModel > xExistingModel;
458
459
0
    {
460
0
        ::osl::MutexGuard aGuard( m_aMutex );
461
0
        xExistingModel = m_xModel;
462
0
        if ( m_bBusy )
463
0
            return false;
464
465
0
        m_bBusy = true;
466
0
    }
467
468
0
    if ( xExistingModel.is() )
469
0
    {
470
0
        try {
471
0
            uno::Reference< frame::XController > xController = xExistingModel->getCurrentController();
472
0
            if ( xController.is() )
473
0
            {
474
0
                uno::Reference< frame::XFrame > xFrame = xController->getFrame();
475
0
                if ( xFrame.is() )
476
0
                {
477
0
                    xFrame->activate();
478
0
                    uno::Reference<awt::XTopWindow> xTopWindow( xFrame->getContainerWindow(), uno::UNO_QUERY );
479
0
                    if(xTopWindow.is())
480
0
                        xTopWindow->toFront();
481
482
0
                    bResult = true;
483
0
                }
484
0
            }
485
0
        }
486
0
        catch( uno::Exception& )
487
0
        {
488
0
        }
489
0
    }
490
0
    else
491
0
    {
492
0
        bResult = CreateModel( m_bUseNative );
493
494
0
        if ( !bResult && !m_bUseNative )
495
0
        {
496
            // the original storage can not be recognized
497
0
            if ( m_aNativeTempURL.isEmpty() )
498
0
            {
499
                // create a temporary file for the native representation if there is no
500
0
                CreateNative();
501
0
            }
502
503
0
            if ( !m_aNativeTempURL.isEmpty() )
504
0
            {
505
0
                bResult = CreateModel( true );
506
0
                if ( bResult )
507
0
                    m_bUseNative = true;
508
0
            }
509
0
        }
510
0
    }
511
512
0
    m_bBusy = false;
513
514
0
    return bResult;
515
0
}
516
517
518
void OwnView_Impl::Close()
519
0
{
520
0
    uno::Reference< frame::XModel > xModel;
521
522
0
    {
523
0
        ::osl::MutexGuard aGuard( m_aMutex );
524
0
        if ( !m_xModel.is() )
525
0
            return;
526
0
        xModel = m_xModel;
527
0
        m_xModel.clear();
528
529
0
        if ( m_bBusy )
530
0
            return;
531
532
0
        m_bBusy = true;
533
0
    }
534
535
0
    try {
536
0
        uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY );
537
0
        if ( xBroadCaster.is() )
538
0
            xBroadCaster->removeEventListener( uno::Reference< document::XEventListener >( this ) );
539
540
0
        uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY );
541
0
        if ( xCloseable.is() )
542
0
        {
543
0
            xCloseable->removeCloseListener( uno::Reference< util::XCloseListener >( this ) );
544
0
            xCloseable->close( true );
545
0
        }
546
0
    }
547
0
    catch( uno::Exception& )
548
0
    {}
549
550
0
    m_bBusy = false;
551
0
}
552
553
554
void SAL_CALL OwnView_Impl::notifyEvent( const document::EventObject& aEvent )
555
0
{
556
557
0
    uno::Reference< frame::XModel > xModel;
558
559
0
    {
560
0
        ::osl::MutexGuard aGuard( m_aMutex );
561
0
        if ( aEvent.Source == m_xModel && aEvent.EventName == "OnSaveAsDone" )
562
0
        {
563
            // SaveAs operation took place, so just forget the model and deregister listeners
564
0
            xModel = m_xModel;
565
0
            m_xModel.clear();
566
0
        }
567
0
    }
568
569
0
    if ( !xModel.is() )
570
0
        return;
571
572
0
    try {
573
0
        uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY );
574
0
        if ( xBroadCaster.is() )
575
0
            xBroadCaster->removeEventListener( uno::Reference< document::XEventListener >( this ) );
576
577
0
        uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY );
578
0
        if ( xCloseable.is() )
579
0
            xCloseable->removeCloseListener( uno::Reference< util::XCloseListener >( this ) );
580
0
    }
581
0
    catch( uno::Exception& )
582
0
    {}
583
0
}
584
585
586
void SAL_CALL OwnView_Impl::queryClosing( const lang::EventObject&, sal_Bool )
587
0
{
588
0
}
589
590
591
void SAL_CALL OwnView_Impl::notifyClosing( const lang::EventObject& Source )
592
0
{
593
0
    ::osl::MutexGuard aGuard( m_aMutex );
594
0
    if ( Source.Source == m_xModel )
595
0
        m_xModel.clear();
596
0
}
597
598
599
void SAL_CALL OwnView_Impl::disposing( const lang::EventObject& Source )
600
0
{
601
0
    ::osl::MutexGuard aGuard( m_aMutex );
602
0
    if ( Source.Source == m_xModel )
603
0
        m_xModel.clear();
604
0
};
605
606
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */