/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
 
#include <basidesh.hxx>
#include <dlged.hxx>
#include <dlgedclip.hxx>
#include <dlgeddef.hxx>
#include <dlgedfac.hxx>
#include <dlgedfunc.hxx>
#include <dlgedmod.hxx>
#include <dlgedobj.hxx>
#include <dlgedpage.hxx>
#include <dlgedview.hxx>
#include <iderdll.hxx>
#include <localizationmgr.hxx>
#include <baside3.hxx>
 
#include <com/sun/star/awt/Toolkit.hpp>
#include <com/sun/star/awt/UnoControlDialog.hpp>
#include <com/sun/star/awt/XDialog.hpp>
#include <com/sun/star/resource/StringResource.hpp>
#include <com/sun/star/util/XCloneable.hpp>
#include <com/sun/star/util/NumberFormatsSupplier.hpp>
#include <comphelper/types.hxx>
#include <sfx2/viewfrm.hxx>
#include <svl/itempool.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <svx/svxids.hrc>
#include <toolkit/helper/vclunohelper.hxx>
#include <vcl/svapp.hxx>
#include <xmlscript/xml_helper.hxx>
#include <xmlscript/xmldlg_imexp.hxx>
 
namespace basctl
{
 
using namespace comphelper;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::io;
 
static const char aResourceResolverPropName[] = "ResourceResolver";
static const char aDecorationPropName[] = "Decoration";
 
 
// DlgEdHint
 
 
DlgEdHint::DlgEdHint(Kind eHint)
    : eKind(eHint)
    , pDlgEdObj(nullptr)
{
}
 
DlgEdHint::DlgEdHint(Kind eHint, DlgEdObj* pObj)
    : eKind(eHint)
    , pDlgEdObj(pObj)
{
}
 
DlgEdHint::~DlgEdHint()
{
}
 
 
// DlgEditor
 
 
void DlgEditor::ShowDialog()
{
    uno::Reference< uno::XComponentContext >  xContext = getProcessComponentContext();
 
    // create a dialog
    uno::Reference< awt::XUnoControlDialog > xDlg = awt::UnoControlDialog::create( xContext );
 
    // clone the dialog model
    uno::Reference< util::XCloneable > xC( m_xUnoControlDialogModel, uno::UNO_QUERY );
    uno::Reference< util::XCloneable > xNew = xC->createClone();
    uno::Reference< awt::XControlModel > xDlgMod( xNew, uno::UNO_QUERY );
 
    uno::Reference< beans::XPropertySet > xSrcDlgModPropSet( m_xUnoControlDialogModel, uno::UNO_QUERY );
    uno::Reference< beans::XPropertySet > xNewDlgModPropSet( xDlgMod, uno::UNO_QUERY );
    if( xNewDlgModPropSet.is() )
    {
        if( xSrcDlgModPropSet.is() )
        {
            try
            {
                Any aResourceResolver = xSrcDlgModPropSet->getPropertyValue( aResourceResolverPropName );
                xNewDlgModPropSet->setPropertyValue( aResourceResolverPropName, aResourceResolver );
            }
            catch(const UnknownPropertyException& )
            {
                OSL_FAIL( "DlgEditor::ShowDialog(): No ResourceResolver property" );
            }
        }
 
        // Disable decoration
        try
        {
            bool bDecoration = true;
 
            Any aDecorationAny = xSrcDlgModPropSet->getPropertyValue( aDecorationPropName );
            aDecorationAny >>= bDecoration;
            if( !bDecoration )
            {
                xNewDlgModPropSet->setPropertyValue( aDecorationPropName, Any( true ) );
                xNewDlgModPropSet->setPropertyValue( "Title", Any( OUString() ) );
            }
        }
        catch(const UnknownPropertyException& )
        {}
    }
 
    // set the model
    xDlg->setModel( xDlgMod );
 
    // create a peer
    uno::Reference< awt::XToolkit> xToolkit = awt::Toolkit::create( xContext );
    xDlg->createPeer( xToolkit, rWindow.GetComponentInterface() );
 
    xDlg->execute();
 
    // need to cast because of multiple inheritance
    Reference<awt::XControl>(xDlg)->dispose();
}
 
 
bool DlgEditor::UnmarkDialog()
{
    SdrObject*      pDlgObj = pDlgEdModel->GetPage(0)->GetObj(0);
    SdrPageView*    pPgView = pDlgEdView->GetSdrPageView();
 
    bool bWasMarked = pDlgEdView->IsObjMarked( pDlgObj );
 
    if( bWasMarked )
        pDlgEdView->MarkObj( pDlgObj, pPgView, true );
 
    return bWasMarked;
}
 
 
bool DlgEditor::RemarkDialog()
{
    SdrObject*      pDlgObj = pDlgEdModel->GetPage(0)->GetObj(0);
    SdrPageView*    pPgView = pDlgEdView->GetSdrPageView();
 
    bool bWasMarked = pDlgEdView->IsObjMarked( pDlgObj );
 
    if( !bWasMarked )
        pDlgEdView->MarkObj( pDlgObj, pPgView );
 
    return bWasMarked;
}
 
 
DlgEditor::DlgEditor (
    vcl::Window& rWindow_, DialogWindowLayout& rLayout_,
    css::uno::Reference<css::frame::XModel> const& xModel,
    css::uno::Reference<css::container::XNameContainer> const & xDialogModel
)
    :pHScroll(nullptr)
    ,pVScroll(nullptr)
    ,pDlgEdModel(new DlgEdModel())
    ,pDlgEdPage(new DlgEdPage(*pDlgEdModel))
    ,m_ClipboardDataFlavors(1)
    ,m_ClipboardDataFlavorsResource(2)
    ,pObjFac(new DlgEdFactory(xModel))
    ,rWindow(rWindow_)
    ,pFunc(new DlgEdFuncSelect(*this))
    ,rLayout(rLayout_)
    ,eMode( DlgEditor::SELECT )
    ,eActObj( OBJ_DLG_PUSHBUTTON )
    ,bFirstDraw(false)
    ,bCreateOK(true)
    ,bDialogModelChanged(false)
    ,aMarkIdle("basctl DlgEditor Mark")
    ,mnPaintGuard(0)
    ,m_xDocument( xModel )
{
    pDlgEdModel->GetItemPool().FreezeIdRanges();
    pDlgEdView.reset(new DlgEdView(*pDlgEdModel, rWindow_, *this));
    pDlgEdModel->SetScaleUnit( MapUnit::Map100thMM );
 
    SdrLayerAdmin& rAdmin = pDlgEdModel->GetLayerAdmin();
    rAdmin.NewLayer( rAdmin.GetControlLayerName() );
    rAdmin.NewLayer( "HiddenLayer" );
 
    pDlgEdModel->InsertPage(pDlgEdPage);
 
    // set clipboard data flavors
    m_ClipboardDataFlavors[0].MimeType =             "application/vnd.sun.xml.dialog" ;
    m_ClipboardDataFlavors[0].HumanPresentableName = "Dialog 6.0" ;
    m_ClipboardDataFlavors[0].DataType =             cppu::UnoType<Sequence< sal_Int8 >>::get();
 
    m_ClipboardDataFlavorsResource[0] =                      m_ClipboardDataFlavors[0];
    m_ClipboardDataFlavorsResource[1].MimeType =             "application/vnd.sun.xml.dialogwithresource" ;
    m_ClipboardDataFlavorsResource[1].HumanPresentableName = "Dialog 8.0" ;
    m_ClipboardDataFlavorsResource[1].DataType =             cppu::UnoType<Sequence< sal_Int8 >>::get();
 
    aMarkIdle.SetInvokeHandler( LINK( this, DlgEditor, MarkTimeout ) );
 
    rWindow.SetMapMode( MapMode( MapUnit::Map100thMM ) );
    pDlgEdPage->SetSize( rWindow.PixelToLogic( Size(DLGED_PAGE_WIDTH_MIN, DLGED_PAGE_HEIGHT_MIN) ) );
 
    pDlgEdView->ShowSdrPage(pDlgEdView->GetModel()->GetPage(0));
    pDlgEdView->SetLayerVisible( "HiddenLayer", false );
    pDlgEdView->SetMoveSnapOnlyTopLeft(true);
    pDlgEdView->SetWorkArea( tools::Rectangle( Point( 0, 0 ), pDlgEdPage->GetSize() ) );
 
    Size aGridSize( 100, 100 );  // 100TH_MM
    pDlgEdView->SetGridCoarse( aGridSize );
    pDlgEdView->SetSnapGridWidth(Fraction(aGridSize.Width(), 1), Fraction(aGridSize.Height(), 1));
    pDlgEdView->SetGridSnap( true );
    pDlgEdView->SetGridVisible( false );
    pDlgEdView->SetDragStripes(false);
 
    pDlgEdView->SetDesignMode();
 
    ::comphelper::disposeComponent( m_xControlContainer );
 
    SetDialog(xDialogModel);
}
 
 
DlgEditor::~DlgEditor()
{
    aMarkIdle.Stop();
 
    ::comphelper::disposeComponent( m_xControlContainer );
}
 
 
Reference< awt::XControlContainer > const & DlgEditor::GetWindowControlContainer()
{
    if (!m_xControlContainer.is())
        m_xControlContainer = VCLUnoHelper::CreateControlContainer(&rWindow);
    return m_xControlContainer;
}
 
 
void DlgEditor::SetScrollBars( ScrollBar* pHS, ScrollBar* pVS )
{
    pHScroll = pHS;
    pVScroll = pVS;
 
    InitScrollBars();
}
 
 
void DlgEditor::InitScrollBars()
{
    DBG_ASSERT( pHScroll, "DlgEditor::InitScrollBars: no horizontal scroll bar!" );
    DBG_ASSERT( pVScroll, "DlgEditor::InitScrollBars: no vertical scroll bar!" );
    if ( !pHScroll || !pVScroll )
        return;
 
    Size aOutSize = rWindow.GetOutputSize();
    Size aPgSize  = pDlgEdPage->GetSize();
 
    pHScroll->SetRange( Range( 0, aPgSize.Width()  ));
    pVScroll->SetRange( Range( 0, aPgSize.Height() ));
    pHScroll->SetVisibleSize( static_cast<sal_uLong>(aOutSize.Width()) );
    pVScroll->SetVisibleSize( static_cast<sal_uLong>(aOutSize.Height()) );
 
    pHScroll->SetLineSize( aOutSize.Width() / 10 );
    pVScroll->SetLineSize( aOutSize.Height() / 10 );
    pHScroll->SetPageSize( aOutSize.Width() / 2 );
    pVScroll->SetPageSize( aOutSize.Height() / 2 );
 
    DoScroll();
}
 
 
void DlgEditor::DoScroll()
{
    if( !pHScroll || !pVScroll )
        return;
 
    MapMode aMap = rWindow.GetMapMode();
    Point aOrg = aMap.GetOrigin();
 
    Size  aScrollPos( pHScroll->GetThumbPos(), pVScroll->GetThumbPos() );
    aScrollPos = rWindow.LogicToPixel( aScrollPos );
    aScrollPos = rWindow.PixelToLogic( aScrollPos );
 
    long  nX   = aScrollPos.Width() + aOrg.X();
    long  nY   = aScrollPos.Height() + aOrg.Y();
 
    if( !nX && !nY )
        return;
 
    rWindow.Update();
 
    // #i31562#
    // When scrolling, someone was rescuing the Wallpaper and forced the window scroll to
    // be done without background refresh. I do not know why, but that causes the repaint
    // problems. Taking that out.
    //  Wallpaper aOldBackground = rWindow.GetBackground();
    //  rWindow.SetBackground();
 
    // #i74769# children should be scrolled
    rWindow.Scroll( -nX, -nY, ScrollFlags::Children);
    aMap.SetOrigin( Point( -aScrollPos.Width(), -aScrollPos.Height() ) );
    rWindow.SetMapMode( aMap );
    rWindow.Update();
 
    DlgEdHint aHint( DlgEdHint::WINDOWSCROLLED );
    Broadcast( aHint );
}
 
 
void DlgEditor::UpdateScrollBars()
{
    MapMode aMap = rWindow.GetMapMode();
    Point aOrg = aMap.GetOrigin();
 
    if ( pHScroll )
        pHScroll->SetThumbPos( -aOrg.X() );
 
    if ( pVScroll )
        pVScroll->SetThumbPos( -aOrg.Y() );
}
 
 
void DlgEditor::SetDialog( const uno::Reference< container::XNameContainer >& xUnoControlDialogModel )
{
    // set dialog model
    m_xUnoControlDialogModel = xUnoControlDialogModel;
 
    // create dialog form
    pDlgEdForm = new DlgEdForm(*pDlgEdModel, *this);
    uno::Reference< awt::XControlModel > xDlgMod( m_xUnoControlDialogModel , uno::UNO_QUERY );
    pDlgEdForm->SetUnoControlModel(xDlgMod);
    static_cast<DlgEdPage*>(pDlgEdModel->GetPage(0))->SetDlgEdForm( pDlgEdForm );
    pDlgEdModel->GetPage(0)->InsertObject( pDlgEdForm );
    AdjustPageSize();
    pDlgEdForm->SetRectFromProps();
    pDlgEdForm->UpdateTabIndices();     // for backward compatibility
    pDlgEdForm->StartListening();
 
    // create controls
    Reference< css::container::XNameAccess > xNameAcc( m_xUnoControlDialogModel, UNO_QUERY );
    if ( xNameAcc.is() )
    {
        // get sequence of control names
        Sequence< OUString > aNames = xNameAcc->getElementNames();
        const OUString* pNames = aNames.getConstArray();
        sal_Int32 nCtrls = aNames.getLength();
 
        // create a map of tab indices and control names, sorted by tab index
        IndexToNameMap aIndexToNameMap;
        for ( sal_Int32 i = 0; i < nCtrls; ++i )
        {
            // get name
            OUString aName( pNames[i] );
 
            // get tab index
            sal_Int16 nTabIndex = -1;
            Any aCtrl = xNameAcc->getByName( aName );
            Reference< css::beans::XPropertySet > xPSet;
               aCtrl >>= xPSet;
            if ( xPSet.is() )
                xPSet->getPropertyValue( DLGED_PROP_TABINDEX ) >>= nTabIndex;
 
            // insert into map
            aIndexToNameMap.emplace( nTabIndex, aName );
        }
 
        // create controls and insert them into drawing page
        for (auto const& indexToName : aIndexToNameMap)
        {
            Any aCtrl = xNameAcc->getByName( indexToName.second );
            Reference< css::awt::XControlModel > xCtrlModel;
            aCtrl >>= xCtrlModel;
            DlgEdObj* pCtrlObj = new DlgEdObj(*pDlgEdModel);
            pCtrlObj->SetUnoControlModel( xCtrlModel );
            pCtrlObj->SetDlgEdForm( pDlgEdForm );
            pDlgEdForm->AddChild( pCtrlObj );
            pDlgEdModel->GetPage(0)->InsertObject( pCtrlObj );
            pCtrlObj->SetRectFromProps();
            pCtrlObj->UpdateStep();
            pCtrlObj->StartListening();
        }
    }
 
    bFirstDraw = true;
 
    pDlgEdModel->SetChanged(false);
}
 
void DlgEditor::ResetDialog ()
{
    DlgEdForm* pOldDlgEdForm = pDlgEdForm;
    DlgEdPage* pPage = static_cast<DlgEdPage*>(pDlgEdModel->GetPage(0));
    SdrPageView* pPgView = pDlgEdView->GetSdrPageView();
    bool bWasMarked = pDlgEdView->IsObjMarked( pOldDlgEdForm );
    pDlgEdView->UnmarkAll();
 
    // clear SdrObjects with broadcasting
    pPage->ClearSdrObjList();
 
    pPage->SetDlgEdForm( nullptr );
    SetDialog( m_xUnoControlDialogModel );
    if( bWasMarked )
        pDlgEdView->MarkObj( pDlgEdForm, pPgView );
}
 
 
Reference< util::XNumberFormatsSupplier > const & DlgEditor::GetNumberFormatsSupplier()
{
    if ( !m_xSupplier.is() )
    {
        Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
        Reference< util::XNumberFormatsSupplier > xSupplier( util::NumberFormatsSupplier::createWithDefaultLocale(xContext) );
 
        ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
        if ( !m_xSupplier.is() )
        {
            m_xSupplier = xSupplier;
        }
    }
    return m_xSupplier;
}
 
 
void DlgEditor::MouseButtonDown( const MouseEvent& rMEvt )
{
    rWindow.GrabFocus();
    pFunc->MouseButtonDown( rMEvt );
}
 
 
void DlgEditor::MouseButtonUp( const MouseEvent& rMEvt )
{
    bool bRet = pFunc->MouseButtonUp( rMEvt );
 
    if( eMode == DlgEditor::INSERT )
        bCreateOK = bRet;
}
 
 
void DlgEditor::MouseMove( const MouseEvent& rMEvt )
{
    pFunc->MouseMove( rMEvt );
}
 
 
bool DlgEditor::KeyInput( const KeyEvent& rKEvt )
{
    return pFunc->KeyInput( rKEvt );
}
 
 
void DlgEditor::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    aPaintRect = rRect;
    mnPaintGuard++;
 
    if (bFirstDraw && rWindow.IsVisible() && (rRenderContext.GetOutputSize() != Size()))
    {
        bFirstDraw = false;
 
        // get property set
        css::uno::Reference<css::beans::XPropertySet> xPSet(pDlgEdForm->GetUnoControlModel(), css::uno::UNO_QUERY);
 
        if (xPSet.is())
        {
            // get dialog size from properties
            sal_Int32 nWidth = 0, nHeight = 0;
            xPSet->getPropertyValue( DLGED_PROP_WIDTH ) >>= nWidth;
            xPSet->getPropertyValue( DLGED_PROP_HEIGHT ) >>= nHeight;
 
            if (nWidth == 0 && nHeight == 0)
            {
                Size   aSize = rRenderContext.PixelToLogic( Size( 400, 300 ) );
 
                // align with grid
                Size aGridSize_(long(pDlgEdView->GetSnapGridWidthX()), long(pDlgEdView->GetSnapGridWidthY()));
                aSize.AdjustWidth( -(aSize.Width()  % aGridSize_.Width()) );
                aSize.AdjustHeight( -(aSize.Height() % aGridSize_.Height()) );
 
                Point  aPos;
                Size   aOutSize = rRenderContext.GetOutputSize();
                aPos.setX( (aOutSize.Width()>>1)  -  (aSize.Width()>>1) );
                aPos.setY( (aOutSize.Height()>>1) -  (aSize.Height()>>1) );
 
                // align with grid
                aPos.AdjustX( -(aPos.X() % aGridSize_.Width()) );
                aPos.AdjustY( -(aPos.Y() % aGridSize_.Height()) );
 
                // don't put in the corner
                Point aMinPos = rRenderContext.PixelToLogic( Point( 30, 20 ) );
                if( (aPos.X() < aMinPos.X()) || (aPos.Y() < aMinPos.Y()) )
                {
                    aPos = aMinPos;
                    aPos.AdjustX( -(aPos.X() % aGridSize_.Width()) );
                    aPos.AdjustY( -(aPos.Y() % aGridSize_.Height()) );
                }
 
                // set dialog position and size
                pDlgEdForm->SetSnapRect( tools::Rectangle( aPos, aSize ) );
                pDlgEdForm->EndListening(false);
                pDlgEdForm->SetPropsFromRect();
                pDlgEdForm->GetDlgEditor().SetDialogModelChanged();
                pDlgEdForm->StartListening();
 
                // set position and size of controls
                if (const size_t nObjCount = pDlgEdPage->GetObjCount())
                {
                    for (size_t i = 0 ; i < nObjCount ; ++i)
                    {
                        if (DlgEdObj* pDlgEdObj = dynamic_cast<DlgEdObj*>(pDlgEdPage->GetObj(i)))
                        {
                            if (!dynamic_cast<DlgEdForm*>(pDlgEdObj))
                            {
                                pDlgEdObj->SetRectFromProps();
                            }
                        }
                    }
                }
            }
        }
    }
 
    // repaint, get PageView and prepare Region
    SdrPageView* pPgView = pDlgEdView->GetSdrPageView();
    const vcl::Region aPaintRectRegion(aPaintRect);
 
    // #i74769#
    SdrPaintWindow* pTargetPaintWindow = nullptr;
 
    // mark repaint start
    if (pPgView)
    {
        pTargetPaintWindow = pPgView->GetView().BeginDrawLayers(&rRenderContext, aPaintRectRegion);
        OSL_ENSURE(pTargetPaintWindow, "BeginDrawLayers: Got no SdrPaintWindow (!)");
    }
 
    // draw background self using wallpaper
    // #i79128# ...and use correct OutDev for that
    if (pTargetPaintWindow)
    {
        OutputDevice& rTargetOutDev = pTargetPaintWindow->GetTargetOutputDevice();
        rTargetOutDev.DrawWallpaper(aPaintRect, Wallpaper(COL_WHITE));
    }
 
    // do paint (unbuffered) and mark repaint end
    if (pPgView)
    {
        // paint of control layer is done in EndDrawLayers anyway...
        pPgView->GetView().EndDrawLayers(*pTargetPaintWindow, true);
    }
 
    mnPaintGuard--;
}
 
 
IMPL_LINK_NOARG(DlgEditor, MarkTimeout, Timer *, void)
{
    rLayout.UpdatePropertyBrowser();
}
 
 
void DlgEditor::SetMode (Mode eNewMode )
{
    if ( eNewMode != eMode )
    {
        if ( eNewMode == INSERT )
            pFunc.reset(new DlgEdFuncInsert(*this));
        else
            pFunc.reset(new DlgEdFuncSelect(*this));
 
        if ( eNewMode == READONLY )
            pDlgEdModel->SetReadOnly( true );
        else
            pDlgEdModel->SetReadOnly( false );
    }
 
    if ( eNewMode == TEST )
        ShowDialog();
 
    eMode = eNewMode;
}
 
 
void DlgEditor::SetInsertObj( sal_uInt16 eObj )
{
    eActObj = eObj;
 
    pDlgEdView->SetCurrentObj( eActObj, SdrInventor::BasicDialog );
}
 
void DlgEditor::CreateDefaultObject()
{
    // create object by factory
    SdrObject* pObj = SdrObjFactory::MakeNewObject(
        *pDlgEdModel,
        pDlgEdView->GetCurrentObjInventor(),
        pDlgEdView->GetCurrentObjIdentifier());
 
    if (DlgEdObj* pDlgEdObj = dynamic_cast<DlgEdObj*>(pObj))
    {
        // set position and size
        Size aSize = rWindow.PixelToLogic( Size( 96, 24 ) );
        Point aPoint = pDlgEdForm->GetSnapRect().Center();
        aPoint.AdjustX( -(aSize.Width() / 2) );
        aPoint.AdjustY( -(aSize.Height() / 2) );
        pDlgEdObj->SetSnapRect( tools::Rectangle( aPoint, aSize ) );
 
        // set default property values
        pDlgEdObj->SetDefaults();
 
        // insert object into drawing page
        SdrPageView* pPageView = pDlgEdView->GetSdrPageView();
        pDlgEdView->InsertObjectAtView( pDlgEdObj, *pPageView);
 
        // start listening
        pDlgEdObj->StartListening();
    }
}
 
 
void DlgEditor::Cut()
{
    Copy();
    Delete();
}
 
 
void implCopyStreamToByteSequence( const Reference< XInputStream >& xStream,
    Sequence< sal_Int8 >& bytes )
{
    xStream->readBytes( bytes, xStream->available() );
    for (;;)
    {
        Sequence< sal_Int8 > readBytes;
        sal_Int32 nRead = xStream->readBytes( readBytes, 1024 );
        if (! nRead)
            break;
 
        sal_Int32 nPos = bytes.getLength();
        bytes.realloc( nPos + nRead );
        memcpy( bytes.getArray() + nPos, readBytes.getConstArray(), static_cast<sal_uInt32>(nRead) );
    }
}
 
void DlgEditor::Copy()
{
    if( !pDlgEdView->AreObjectsMarked() )
        return;
 
    // stop all drawing actions
    pDlgEdView->BrkAction();
 
    // create an empty clipboard dialog model
    Reference< util::XCloneable > xClone( m_xUnoControlDialogModel, UNO_QUERY );
    Reference< util::XCloneable > xNewClone = xClone->createClone();
    Reference< container::XNameContainer > xClipDialogModel( xNewClone, UNO_QUERY );
 
    Reference< container::XNameAccess > xNAcc( xClipDialogModel, UNO_QUERY );
    if ( xNAcc.is() )
    {
           Sequence< OUString > aNames = xNAcc->getElementNames();
           const OUString* pNames = aNames.getConstArray();
        sal_uInt32 nCtrls = aNames.getLength();
 
        for ( sal_uInt32 n = 0; n < nCtrls; n++ )
        {
               xClipDialogModel->removeByName( pNames[n] );
        }
    }
 
    // insert control models of marked objects into clipboard dialog model
    const size_t nMark = pDlgEdView->GetMarkedObjectList().GetMarkCount();
    for( size_t i = 0; i < nMark; ++i )
    {
        SdrObject* pObj = pDlgEdView->GetMarkedObjectList().GetMark(i)->GetMarkedSdrObj();
        DlgEdObj* pDlgEdObj = dynamic_cast<DlgEdObj*>(pObj);
 
        if (pDlgEdObj && !dynamic_cast<DlgEdForm*>(pDlgEdObj))
        {
            OUString aName;
            Reference< beans::XPropertySet >  xMarkPSet(pDlgEdObj->GetUnoControlModel(), uno::UNO_QUERY);
            if (xMarkPSet.is())
            {
                xMarkPSet->getPropertyValue( DLGED_PROP_NAME ) >>= aName;
            }
 
            Reference< container::XNameAccess > xNameAcc(m_xUnoControlDialogModel, UNO_QUERY );
            if ( xNameAcc.is() && xNameAcc->hasByName(aName) )
            {
                Any aCtrl = xNameAcc->getByName( aName );
 
                // clone control model
                Reference< util::XCloneable > xCtrl;
                   aCtrl >>= xCtrl;
                Reference< util::XCloneable > xNewCtrl = xCtrl->createClone();
                Any aNewCtrl;
                aNewCtrl <<= xNewCtrl;
 
                if (xClipDialogModel.is())
                    xClipDialogModel->insertByName( aName , aNewCtrl );
            }
        }
    }
 
    // export clipboard dialog model to xml
    Reference< XComponentContext > xContext(
        comphelper::getProcessComponentContext() );
    Reference< XInputStreamProvider > xISP = ::xmlscript::exportDialogModel( xClipDialogModel, xContext, m_xDocument );
    Reference< XInputStream > xStream( xISP->createInputStream() );
    Sequence< sal_Int8 > DialogModelBytes;
    implCopyStreamToByteSequence( xStream, DialogModelBytes );
    xStream->closeInput();
 
    // set clipboard content
    Reference< datatransfer::clipboard::XClipboard > xClipboard = GetWindow().GetClipboard();
    if ( xClipboard.is() )
    {
        // With resource?
        uno::Reference< beans::XPropertySet > xDialogModelPropSet( m_xUnoControlDialogModel, uno::UNO_QUERY );
        uno::Reference< resource::XStringResourcePersistence > xStringResourcePersistence;
        if( xDialogModelPropSet.is() )
        {
            try
            {
                Any aResourceResolver = xDialogModelPropSet->getPropertyValue( aResourceResolverPropName );
                aResourceResolver >>= xStringResourcePersistence;
            }
            catch(const UnknownPropertyException& )
            {}
        }
 
        DlgEdTransferableImpl* pTrans = nullptr;
        if( xStringResourcePersistence.is() )
        {
            // With resource, support old and new format
 
            // Export xClipDialogModel another time with ids replaced by current language string
            uno::Reference< resource::XStringResourceManager >
                xStringResourceManager( xStringResourcePersistence, uno::UNO_QUERY );
            LocalizationMgr::resetResourceForDialog( xClipDialogModel, xStringResourceManager );
            Reference< XInputStreamProvider > xISP2 = ::xmlscript::exportDialogModel( xClipDialogModel, xContext, m_xDocument );
            Reference< XInputStream > xStream2( xISP2->createInputStream() );
            Sequence< sal_Int8 > NoResourceDialogModelBytes;
            implCopyStreamToByteSequence( xStream2, NoResourceDialogModelBytes );
            xStream2->closeInput();
 
            // Old format contains dialog with replaced ids
            Sequence< Any > aSeqData(2);
            Any aNoResourceDialogModelBytesAny;
            aNoResourceDialogModelBytesAny <<= NoResourceDialogModelBytes;
            aSeqData[0] = aNoResourceDialogModelBytesAny;
 
            // New format contains dialog and resource
            Sequence< sal_Int8 > aResData = xStringResourcePersistence->exportBinary();
 
            // Create sequence for combined dialog and resource
            sal_Int32 nDialogDataLen = DialogModelBytes.getLength();
            sal_Int32 nResDataLen = aResData.getLength();
 
            // Combined data = 4 Bytes 32Bit Offset to begin of resource data, lowest byte first
            // + nDialogDataLen bytes dialog data + nResDataLen resource data
            sal_Int32 nTotalLen = 4 + nDialogDataLen + nResDataLen;
            sal_Int32 nResOffset = 4 + nDialogDataLen;
            Sequence< sal_Int8 > aCombinedData( nTotalLen );
            sal_Int8* pCombinedData = aCombinedData.getArray();
 
            // Write offset
            sal_Int32 n = nResOffset;
            for( sal_Int16 i = 0 ; i < 4 ; i++ )
            {
                pCombinedData[i] = sal_Int8( n & 0xff );
                n >>= 8;
            }
            memcpy( pCombinedData + 4, DialogModelBytes.getConstArray(), nDialogDataLen );
            memcpy( pCombinedData + nResOffset, aResData.getConstArray(), nResDataLen );
 
            aSeqData[1] <<= aCombinedData;
 
            pTrans = new DlgEdTransferableImpl( m_ClipboardDataFlavorsResource, aSeqData );
        }
        else
        {
            // No resource, support only old format
            Sequence< Any > aSeqData(1);
            Any aDialogModelBytesAny;
            aDialogModelBytesAny <<= DialogModelBytes;
            aSeqData[0] = aDialogModelBytesAny;
            pTrans = new DlgEdTransferableImpl( m_ClipboardDataFlavors , aSeqData );
        }
        SolarMutexReleaser aReleaser;
        xClipboard->setContents( pTrans , pTrans );
    }
}
 
 
void DlgEditor::Paste()
{
    // stop all drawing actions
    pDlgEdView->BrkAction();
 
    // unmark all objects
    pDlgEdView->UnmarkAll();
 
    // get clipboard
    Reference< datatransfer::clipboard::XClipboard > xClipboard = GetWindow().GetClipboard();
    if ( xClipboard.is() )
    {
        Reference< datatransfer::XTransferable > xTransf;
        {
            SolarMutexReleaser aReleaser;
            // get clipboard content
            xTransf = xClipboard->getContents();
        }
        if ( xTransf.is() )
        {
            // Is target dialog (library) localized?
            uno::Reference< beans::XPropertySet > xDialogModelPropSet( m_xUnoControlDialogModel, uno::UNO_QUERY );
            uno::Reference< resource::XStringResourceManager > xStringResourceManager;
            if( xDialogModelPropSet.is() )
            {
                try
                {
                    Any aResourceResolver = xDialogModelPropSet->getPropertyValue( aResourceResolverPropName );
                    aResourceResolver >>= xStringResourceManager;
                }
                catch(const UnknownPropertyException& )
                {}
            }
            bool bLocalized = false;
            if( xStringResourceManager.is() )
                bLocalized = ( xStringResourceManager->getLocales().getLength() > 0 );
 
            if ( xTransf->isDataFlavorSupported( m_ClipboardDataFlavors[0] ) )
            {
                // create clipboard dialog model from xml
                Reference< XComponentContext > xContext = comphelper::getProcessComponentContext();
                Reference< container::XNameContainer > xClipDialogModel( xContext->getServiceManager()->createInstanceWithContext(
                    "com.sun.star.awt.UnoControlDialogModel", xContext ), uno::UNO_QUERY );
 
                bool bSourceIsLocalized = false;
                Sequence< sal_Int8 > DialogModelBytes;
                Sequence< sal_Int8 > aResData;
                if( bLocalized && xTransf->isDataFlavorSupported( m_ClipboardDataFlavorsResource[1] ) )
                {
                    bSourceIsLocalized = true;
 
                    Any aCombinedDataAny = xTransf->getTransferData( m_ClipboardDataFlavorsResource[1] );
                    Sequence< sal_Int8 > aCombinedData;
                    aCombinedDataAny >>= aCombinedData;
                    const sal_Int8* pCombinedData = aCombinedData.getConstArray();
 
                    sal_Int32 nTotalLen = aCombinedData.getLength();
 
                    // Reading offset
                    sal_Int32 nResOffset = 0;
                    sal_Int32 nFactor = 1;
                    for( sal_Int16 i = 0; i < 4; i++ )
                    {
                        nResOffset += nFactor * sal_uInt8( pCombinedData[i] );
                        nFactor *= 256;
                    }
 
                    sal_Int32 nResDataLen = nTotalLen - nResOffset;
                    sal_Int32 nDialogDataLen = nTotalLen - nResDataLen - 4;
 
                    DialogModelBytes.realloc( nDialogDataLen );
                    memcpy( DialogModelBytes.getArray(), pCombinedData + 4, nDialogDataLen );
 
                    aResData.realloc( nResDataLen );
                    memcpy( aResData.getArray(), pCombinedData + nResOffset, nResDataLen );
                }
                else
                {
                    Any aAny = xTransf->getTransferData( m_ClipboardDataFlavors[0] );
                    aAny >>= DialogModelBytes;
                }
 
                if ( xClipDialogModel.is() )
                {
                    Reference<XInputStream> xIn = ::xmlscript::createInputStream( DialogModelBytes.getConstArray(), DialogModelBytes.getLength() );
                    ::xmlscript::importDialogModel( xIn , xClipDialogModel, xContext, m_xDocument );
                }
 
                // get control models from clipboard dialog model
                Reference< css::container::XNameAccess > xNameAcc( xClipDialogModel, UNO_QUERY );
                if ( xNameAcc.is() )
                {
                       Sequence< OUString > aNames = xNameAcc->getElementNames();
                       const OUString* pNames = aNames.getConstArray();
                    sal_uInt32 nCtrls = aNames.getLength();
 
                    Reference< resource::XStringResourcePersistence > xStringResourcePersistence;
                    if( nCtrls > 0 && bSourceIsLocalized )
                    {
                        xStringResourcePersistence = css::resource::StringResource::create( getProcessComponentContext() );
                        xStringResourcePersistence->importBinary( aResData );
                    }
                    for( sal_uInt32 n = 0; n < nCtrls; n++ )
                    {
                           Any aA = xNameAcc->getByName( pNames[n] );
                        Reference< css::awt::XControlModel > xCM;
                           aA >>= xCM;
 
                        // clone the control model
                        Reference< util::XCloneable > xClone( xCM, uno::UNO_QUERY );
                        Reference< awt::XControlModel > xCtrlModel( xClone->createClone(), uno::UNO_QUERY );
 
                        DlgEdObj* pCtrlObj = new DlgEdObj(*pDlgEdModel);
                        pCtrlObj->SetDlgEdForm(pDlgEdForm);         // set parent form
                        pDlgEdForm->AddChild(pCtrlObj);             // add child to parent form
                        pCtrlObj->SetUnoControlModel( xCtrlModel ); // set control model
 
                        // set new name
                        OUString aOUniqueName( pCtrlObj->GetUniqueName() );
                        Reference< beans::XPropertySet > xPSet( xCtrlModel , UNO_QUERY );
                        xPSet->setPropertyValue( DLGED_PROP_NAME, Any(aOUniqueName) );
 
                        // set tabindex
                        Reference< container::XNameAccess > xNA( m_xUnoControlDialogModel , UNO_QUERY );
                           Sequence< OUString > aNames_ = xNA->getElementNames();
                        xPSet->setPropertyValue( DLGED_PROP_TABINDEX, Any(static_cast<sal_Int16>(aNames_.getLength())) );
 
                        if( bLocalized )
                        {
                            Any aControlAny;
                            aControlAny <<= xCtrlModel;
                            if( bSourceIsLocalized && xStringResourcePersistence.is() )
                            {
                                Reference< resource::XStringResourceResolver >
                                    xSourceStringResolver( xStringResourcePersistence, UNO_QUERY );
                                LocalizationMgr::copyResourcesForPastedEditorObject( this,
                                    aControlAny, aOUniqueName, xSourceStringResolver );
                            }
                            else
                            {
                                LocalizationMgr::setControlResourceIDsForNewEditorObject
                                    ( this, aControlAny, aOUniqueName );
                            }
                        }
 
                        // insert control model in editor dialog model
                        Any aCtrlModel;
                        aCtrlModel <<= xCtrlModel;
                        m_xUnoControlDialogModel->insertByName( aOUniqueName , aCtrlModel );
 
                        // insert object into drawing page
                        pDlgEdModel->GetPage(0)->InsertObject( pCtrlObj );
                        pCtrlObj->SetRectFromProps();
                        pCtrlObj->UpdateStep();
                        pDlgEdForm->UpdateTabOrderAndGroups();
                        pCtrlObj->StartListening();                         // start listening
 
                        // mark object
                        SdrPageView* pPgView = pDlgEdView->GetSdrPageView();
                        pDlgEdView->MarkObj( pCtrlObj, pPgView, false, true);
                    }
 
                    // center marked objects in dialog editor form
                    Point aMarkCenter = pDlgEdView->GetMarkedObjRect().Center();
                    Point aFormCenter = pDlgEdForm->GetSnapRect().Center();
                    Point aPoint = aFormCenter - aMarkCenter;
                    Size  aSize( aPoint.X() , aPoint.Y() );
                    pDlgEdView->MoveMarkedObj( aSize );                     // update of control model properties (position + size) in NbcMove
                    pDlgEdView->MarkListHasChanged();
 
                    // dialog model changed
                    SetDialogModelChanged();
                }
            }
        }
    }
}
 
 
void DlgEditor::Delete()
{
    if( !pDlgEdView->AreObjectsMarked() )
        return;
 
    // remove control models of marked objects from dialog model
    const size_t nMark = pDlgEdView->GetMarkedObjectList().GetMarkCount();
 
    for( size_t i = 0; i < nMark; ++i )
    {
        SdrObject* pObj = pDlgEdView->GetMarkedObjectList().GetMark(i)->GetMarkedSdrObj();
        DlgEdObj* pDlgEdObj = dynamic_cast<DlgEdObj*>(pObj);
 
        if ( pDlgEdObj && !dynamic_cast<DlgEdForm*>(pDlgEdObj) )
        {
            // get name from property
            OUString aName;
            uno::Reference< beans::XPropertySet >  xPSet(pDlgEdObj->GetUnoControlModel(), uno::UNO_QUERY);
            if (xPSet.is())
            {
                xPSet->getPropertyValue( DLGED_PROP_NAME ) >>= aName;
            }
 
            // remove control from dialog model
            Reference< css::container::XNameAccess > xNameAcc(pDlgEdObj->GetDlgEdForm()->GetUnoControlModel(), UNO_QUERY );
            if ( xNameAcc.is() && xNameAcc->hasByName(aName) )
            {
                Reference< css::container::XNameContainer > xCont(xNameAcc, UNO_QUERY );
                if ( xCont.is() )
                {
                    if( xCont->hasByName( aName ) )
                    {
                        Any aAny = xCont->getByName( aName );
                        LocalizationMgr::deleteControlResourceIDsForDeletedEditorObject( this, aAny, aName );
                    }
                    xCont->removeByName( aName );
                }
            }
 
            // remove child from parent form
            pDlgEdForm->RemoveChild( pDlgEdObj );
        }
    }
 
    // update tab indices
    pDlgEdForm->UpdateTabIndices();
 
    pDlgEdView->BrkAction();
 
    bool const bDlgMarked = UnmarkDialog();
    pDlgEdView->DeleteMarked();
    if( bDlgMarked )
        RemarkDialog();
}
 
 
bool DlgEditor::IsPasteAllowed()
{
    // get clipboard
    Reference< datatransfer::clipboard::XClipboard > xClipboard = GetWindow().GetClipboard();
    if ( xClipboard.is() )
    {
        Reference< datatransfer::XTransferable > xTransf;
        {
            SolarMutexReleaser aReleaser;
            // get clipboard content
            xTransf = xClipboard->getContents();
        }
        if (xTransf.is())
            return xTransf->isDataFlavorSupported(m_ClipboardDataFlavors[0]);
    }
    return false;
}
 
 
void DlgEditor::ShowProperties()
{
    rLayout.ShowPropertyBrowser();
}
 
 
void DlgEditor::UpdatePropertyBrowserDelayed()
{
    aMarkIdle.Start();
}
 
 
bool DlgEditor::IsModified() const
{
    return pDlgEdModel->IsChanged() || bDialogModelChanged;
}
 
 
void DlgEditor::ClearModifyFlag()
{
    pDlgEdModel->SetChanged(false);
    bDialogModelChanged = false;
}
 
 
namespace Print
{
    long const nLeftMargin = 1700;
    long const nRightMargin = 900;
    long const nTopMargin = 2000;
    long const nBottomMargin = 1000;
    long const nBorder = 300;
}
 
void lcl_PrintHeader( Printer* pPrinter, const OUString& rTitle ) // not working yet
{
 
    pPrinter->Push();
 
    Size const aSz = pPrinter->GetOutputSize();
 
    pPrinter->SetLineColor( COL_BLACK );
    pPrinter->SetFillColor();
 
    vcl::Font aFont( pPrinter->GetFont() );
    aFont.SetWeight( WEIGHT_BOLD );
    aFont.SetAlignment( ALIGN_BOTTOM );
    pPrinter->SetFont( aFont );
 
    long const nFontHeight = pPrinter->GetTextHeight();
 
    // 1st border => line, 2+3 border = free space
    long const nYTop = Print::nTopMargin - 3*Print::nBorder - nFontHeight;
 
    long const nXLeft = Print::nLeftMargin - Print::nBorder;
    long const nXRight = aSz.Width() - Print::nRightMargin + Print::nBorder;
 
    pPrinter->DrawRect(tools::Rectangle(
        Point(nXLeft, nYTop),
        Size(nXRight - nXLeft, aSz.Height() - nYTop - Print::nBottomMargin + Print::nBorder)
    ));
 
    long nY = Print::nTopMargin - 2*Print::nBorder;
    Point aPos(Print::nLeftMargin, nY);
    pPrinter->DrawText( aPos, rTitle );
 
    nY = Print::nTopMargin - Print::nBorder;
    pPrinter->DrawLine( Point( nXLeft, nY ), Point( nXRight, nY ) );
 
    pPrinter->Pop();
}
 
 
void DlgEditor::printPage( sal_Int32 nPage, Printer* pPrinter, const OUString& rTitle )
{
    if( nPage == 0 )
        Print( pPrinter, rTitle );
}
 
 
void DlgEditor::Print( Printer* pPrinter, const OUString& rTitle )    // not working yet
{
    MapMode aOldMap( pPrinter->GetMapMode());
    vcl::Font aOldFont( pPrinter->GetFont() );
 
    MapMode aMap( MapUnit::Map100thMM );
    pPrinter->SetMapMode( aMap );
    vcl::Font aFont;
    aFont.SetAlignment( ALIGN_BOTTOM );
    aFont.SetFontSize( Size( 0, 360 ));
    pPrinter->SetFont( aFont );
 
    Size aPaperSz = pPrinter->GetOutputSize();
    aPaperSz.AdjustWidth( -(Print::nLeftMargin + Print::nRightMargin) );
    aPaperSz.AdjustHeight( -(Print::nTopMargin + Print::nBottomMargin) );
 
    lcl_PrintHeader( pPrinter, rTitle );
 
    Bitmap aDlg;
    Size aBmpSz( pPrinter->PixelToLogic( aDlg.GetSizePixel() ) );
    double nPaperSzWidth = aPaperSz.Width();
    double nPaperSzHeight = aPaperSz.Height();
    double nBmpSzWidth = aBmpSz.Width();
    double nBmpSzHeight = aBmpSz.Height();
    double nScaleX = (nPaperSzWidth / nBmpSzWidth );
    double nScaleY = (nPaperSzHeight / nBmpSzHeight );
 
    Size aOutputSz;
    if( nBmpSzHeight * nScaleX <= nPaperSzHeight )
    {
        aOutputSz.setWidth( static_cast<long>(nBmpSzWidth * nScaleX) );
        aOutputSz.setHeight( static_cast<long>(nBmpSzHeight * nScaleX) );
    }
    else
    {
        aOutputSz.setWidth( static_cast<long>(nBmpSzWidth * nScaleY) );
        aOutputSz.setHeight( static_cast<long>(nBmpSzHeight * nScaleY) );
    }
 
    Point aPosOffs(
        (aPaperSz.Width() / 2) - (aOutputSz.Width() / 2),
        (aPaperSz.Height()/ 2) - (aOutputSz.Height() / 2));
 
    aPosOffs.AdjustX(Print::nLeftMargin );
    aPosOffs.AdjustY(Print::nTopMargin );
 
    pPrinter->DrawBitmap( aPosOffs, aOutputSz, aDlg );
 
    pPrinter->SetMapMode( aOldMap );
    pPrinter->SetFont( aOldFont );
}
 
 
bool DlgEditor::AdjustPageSize()
{
    bool bAdjustedPageSize = false;
    Reference< beans::XPropertySet > xPSet( m_xUnoControlDialogModel, UNO_QUERY );
    if ( xPSet.is() )
    {
        sal_Int32 nFormXIn = 0, nFormYIn = 0, nFormWidthIn = 0, nFormHeightIn = 0;
        xPSet->getPropertyValue( DLGED_PROP_POSITIONX ) >>= nFormXIn;
        xPSet->getPropertyValue( DLGED_PROP_POSITIONY ) >>= nFormYIn;
        xPSet->getPropertyValue( DLGED_PROP_WIDTH ) >>= nFormWidthIn;
        xPSet->getPropertyValue( DLGED_PROP_HEIGHT ) >>= nFormHeightIn;
 
        sal_Int32 nFormX, nFormY, nFormWidth, nFormHeight;
        if ( pDlgEdForm && pDlgEdForm->TransformFormToSdrCoordinates( nFormXIn, nFormYIn, nFormWidthIn, nFormHeightIn, nFormX, nFormY, nFormWidth, nFormHeight ) )
        {
            Size aPageSizeDelta( 400, 300 );
            aPageSizeDelta = rWindow.PixelToLogic( aPageSizeDelta, MapMode( MapUnit::Map100thMM ) );
 
            sal_Int32 nNewPageWidth = nFormX + nFormWidth + aPageSizeDelta.Width();
            sal_Int32 nNewPageHeight = nFormY + nFormHeight + aPageSizeDelta.Height();
 
            Size aPageSizeMin( DLGED_PAGE_WIDTH_MIN, DLGED_PAGE_HEIGHT_MIN );
            aPageSizeMin = rWindow.PixelToLogic( aPageSizeMin, MapMode( MapUnit::Map100thMM ) );
            sal_Int32 nPageWidthMin = aPageSizeMin.Width();
            sal_Int32 nPageHeightMin = aPageSizeMin.Height();
 
            if ( nNewPageWidth < nPageWidthMin )
                nNewPageWidth = nPageWidthMin;
 
            if ( nNewPageHeight < nPageHeightMin )
                nNewPageHeight = nPageHeightMin;
 
            if ( pDlgEdPage )
            {
                Size aPageSize = pDlgEdPage->GetSize();
                if ( nNewPageWidth != aPageSize.Width() || nNewPageHeight != aPageSize.Height() )
                {
                    Size aNewPageSize( nNewPageWidth, nNewPageHeight );
                    pDlgEdPage->SetSize( aNewPageSize );
                    pDlgEdView->SetWorkArea( tools::Rectangle( Point( 0, 0 ), aNewPageSize ) );
                    bAdjustedPageSize = true;
                }
            }
        }
    }
 
    return bAdjustedPageSize;
}
 
 
} // namespace basctl
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V560 A part of conditional expression is always true: nHeight == 0.

V560 A part of conditional expression is always true: nWidth == 0.