/* -*- 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 <sal/config.h>
#include <config_folders.h>
 
#include <comphelper/lok.hxx>
#include <i18nutil/unicode.hxx>
#include <o3tl/test_info.hxx>
#include <o3tl/untaint.hxx>
#include <officecfg/Office/Common.hxx>
#include <tools/stream.hxx>
#include <vcl/event.hxx>
#include <vcl/svapp.hxx>
#include <vcl/fieldvalues.hxx>
#include <vcl/settings.hxx>
#include <vcl/image.hxx>
#include <vcl/virdev.hxx>
#include <vcl/weld/MetricSpinButton.hxx>
#include <vcl/weld/customweld.hxx>
#include <vcl/weld/weldutils.hxx>
#include <rtl/math.hxx>
#include <sal/macros.h>
#include <sal/log.hxx>
#include <comphelper/string.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/syslocale.hxx>
 
#include <svtools/borderline.hxx>
#include <svtools/sampletext.hxx>
#include <svtools/svtresid.hxx>
#include <svtools/strings.hrc>
#include <svtools/ctrlbox.hxx>
#include <svtools/ctrltool.hxx>
#include <svtools/borderhelper.hxx>
#include <svtools/valueset.hxx>
 
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <editeng/borderline.hxx>
 
#include <rtl/bootstrap.hxx>
 
#include <borderline.hrc>
 
#include <stdio.h>
 
#define IMGOUTERTEXTSPACE 5
#define EXTRAFONTSIZE 5
#define GAPTOEXTRAPREVIEW 10
#define MINGAPWIDTH 2
 
constexpr OUStringLiteral FONTNAMEBOXMRUENTRIESFILE = u"/user/config/fontnameboxmruentries";
 
 
BorderWidthImpl::BorderWidthImpl( BorderWidthImplFlags nFlags, double nRate1, double nRate2, double nRateGap ):
    m_nFlags( nFlags ),
    m_nRate1( nRate1 ),
    m_nRate2( nRate2 ),
    m_nRateGap( nRateGap )
{
}
 
bool BorderWidthImpl::operator== ( const BorderWidthImpl& r ) const
{
    return ( m_nFlags == r.m_nFlags ) &&
           ( m_nRate1 == r.m_nRate1 ) &&
           ( m_nRate2 == r.m_nRate2 ) &&
           ( m_nRateGap == r.m_nRateGap );
}
 
tools::Long BorderWidthImpl::GetLine1( tools::Long nWidth ) const
{
    tools::Long result = static_cast<tools::Long>(m_nRate1);
    if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 )
    {
        tools::Long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2;
        tools::Long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap;
        result = std::max<tools::Long>(0,
                    static_cast<tools::Long>((m_nRate1 * nWidth) + 0.5)
                        - (nConstant2 + nConstantD));
        if (result == 0 && m_nRate1 > 0.0 && nWidth > 0)
        {   // fdo#51777: hack to essentially treat 1 twip DOUBLE border
            result = 1;  // as 1 twip SINGLE border
        }
    }
    return result;
}
 
tools::Long BorderWidthImpl::GetLine2( tools::Long nWidth ) const
{
    tools::Long result = static_cast<tools::Long>(m_nRate2);
    if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2)
    {
        tools::Long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1;
        tools::Long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap;
        result = std::max<tools::Long>(0,
                    static_cast<tools::Long>((m_nRate2 * nWidth) + 0.5)
                        - (nConstant1 + nConstantD));
    }
    return result;
}
 
tools::Long BorderWidthImpl::GetGap( tools::Long nWidth ) const
{
    tools::Long result = static_cast<tools::Long>(m_nRateGap);
    if ( m_nFlags & BorderWidthImplFlags::CHANGE_DIST )
    {
        tools::Long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1;
        tools::Long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2;
        result = std::max<tools::Long>(0,
                    static_cast<tools::Long>((m_nRateGap * nWidth) + 0.5)
                        - (nConstant1 + nConstant2));
    }
 
    // Avoid having too small distances (less than 0.1pt)
    if ( result < MINGAPWIDTH && m_nRate1 > 0 && m_nRate2 > 0 )
        result = MINGAPWIDTH;
 
    return result;
}
 
static double lcl_getGuessedWidth( tools::Long nTested, double nRate, bool bChanging )
{
    double nWidth = -1.0;
    if ( bChanging )
        nWidth = double( nTested ) / nRate;
    else
    {
        if ( rtl::math::approxEqual(double( nTested ), nRate) )
            nWidth = nRate;
    }
 
    return nWidth;
}
 
tools::Long BorderWidthImpl::GuessWidth( tools::Long nLine1, tools::Long nLine2, tools::Long nGap )
{
    std::vector< double > aToCompare;
    bool bInvalid = false;
 
    bool bLine1Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 );
    double nWidth1 = lcl_getGuessedWidth( nLine1, m_nRate1, bLine1Change );
    if ( bLine1Change )
        aToCompare.push_back( nWidth1 );
    else if (nWidth1 < 0)
        bInvalid = true;
 
    bool bLine2Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2 );
    double nWidth2 = lcl_getGuessedWidth( nLine2, m_nRate2, bLine2Change );
    if ( bLine2Change )
        aToCompare.push_back( nWidth2 );
    else if (nWidth2 < 0)
        bInvalid = true;
 
    bool bGapChange = bool( m_nFlags & BorderWidthImplFlags::CHANGE_DIST );
    double nWidthGap = lcl_getGuessedWidth( nGap, m_nRateGap, bGapChange );
    if ( bGapChange && nGap >= MINGAPWIDTH )
        aToCompare.push_back( nWidthGap );
    else if ( !bGapChange && nWidthGap < 0 )
        bInvalid = true;
 
    // non-constant line width factors must sum to 1
    assert((((bLine1Change) ? m_nRate1 : 0) +
            ((bLine2Change) ? m_nRate2 : 0) +
            ((bGapChange) ? m_nRateGap : 0)) - 1.0 < 0.00001 );
 
    double nWidth = 0.0;
    if ( (!bInvalid) && (!aToCompare.empty()) )
    {
        nWidth = *aToCompare.begin();
        for (auto const& elem : aToCompare)
        {
            bInvalid = ( nWidth != elem );
            if (bInvalid)
                break;
        }
        nWidth = bInvalid ?  0.0 : nLine1 + nLine2 + nGap;
    }
 
    return nWidth;
}
 
static void lclDrawPolygon( OutputDevice& rDev, const basegfx::B2DPolygon& rPolygon, tools::Long nWidth, SvxBorderLineStyle nDashing )
{
    AntialiasingFlags nOldAA = rDev.GetAntialiasing();
    rDev.SetAntialiasing( nOldAA & ~AntialiasingFlags::Enable );
 
    tools::Long nPix = rDev.PixelToLogic(Size(1, 1)).Width();
    basegfx::B2DPolyPolygon aPolygons = svtools::ApplyLineDashing(rPolygon, nDashing, nPix);
 
    // Handle problems of width 1px in Pixel mode: 0.5px gives a 1px line
    if (rDev.GetMapMode().GetMapUnit() == MapUnit::MapPixel && nWidth == nPix)
        nWidth = 0;
 
    for ( sal_uInt32 i = 0; i < aPolygons.count( ); i++ )
    {
        const basegfx::B2DPolygon& aDash = aPolygons.getB2DPolygon( i );
        basegfx::B2DPoint aStart = aDash.getB2DPoint( 0 );
        basegfx::B2DPoint aEnd = aDash.getB2DPoint( aDash.count() - 1 );
 
        basegfx::B2DVector aVector( aEnd - aStart );
        aVector.normalize( );
        const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector));
 
        const basegfx::B2DVector aWidthOffset( double( nWidth ) / 2 * aPerpendicular);
        basegfx::B2DPolygon aDashPolygon;
        aDashPolygon.append( aStart + aWidthOffset );
        aDashPolygon.append( aEnd + aWidthOffset );
        aDashPolygon.append( aEnd - aWidthOffset );
        aDashPolygon.append( aStart - aWidthOffset );
        aDashPolygon.setClosed( true );
 
        rDev.DrawPolygon( aDashPolygon );
    }
 
    rDev.SetAntialiasing( nOldAA );
}
 
namespace svtools {
 
/**
 * Dashing array must start with a line width and end with a blank width.
 */
static std::vector<double> GetDashing( SvxBorderLineStyle nDashing )
{
    std::vector<double> aPattern;
    switch (nDashing)
    {
        case SvxBorderLineStyle::DOTTED:
            aPattern.push_back( 1.0 ); // line
            aPattern.push_back( 2.0 ); // blank
        break;
        case SvxBorderLineStyle::DASHED:
            aPattern.push_back( 16.0 ); // line
            aPattern.push_back( 5.0 );  // blank
        break;
        case SvxBorderLineStyle::FINE_DASHED:
            aPattern.push_back( 6.0 ); // line
            aPattern.push_back( 2.0 ); // blank
        break;
        case SvxBorderLineStyle::DASH_DOT:
            aPattern.push_back( 16.0 ); // line
            aPattern.push_back( 5.0 );  // blank
            aPattern.push_back( 5.0 );  // line
            aPattern.push_back( 5.0 );  // blank
        break;
        case SvxBorderLineStyle::DASH_DOT_DOT:
            aPattern.push_back( 16.0 ); // line
            aPattern.push_back( 5.0 );  // blank
            aPattern.push_back( 5.0 );  // line
            aPattern.push_back( 5.0 );  // blank
            aPattern.push_back( 5.0 );  // line
            aPattern.push_back( 5.0 );  // blank
        break;
        default:
            ;
    }
 
    return aPattern;
}
 
namespace {
 
class ApplyScale
{
    double mfScale;
public:
    explicit ApplyScale( double fScale ) : mfScale(fScale) {}
    void operator() ( double& rVal )
    {
        rVal *= mfScale;
    }
};
 
}
 
std::vector<double> GetLineDashing( SvxBorderLineStyle nDashing, double fScale )
{
    std::vector<double> aPattern = GetDashing(nDashing);
    std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale));
    return aPattern;
}
 
basegfx::B2DPolyPolygon ApplyLineDashing( const basegfx::B2DPolygon& rPolygon, SvxBorderLineStyle nDashing, double fScale )
{
    std::vector<double> aPattern = GetDashing(nDashing);
    std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale));
 
    basegfx::B2DPolyPolygon aPolygons;
 
    if (aPattern.empty())
        aPolygons.append(rPolygon);
    else
        basegfx::utils::applyLineDashing(rPolygon, aPattern, &aPolygons);
 
    return aPolygons;
}
 
void DrawLine( OutputDevice& rDev, const Point& rP1, const Point& rP2,
    sal_uInt32 nWidth, SvxBorderLineStyle nDashing )
{
    DrawLine( rDev, basegfx::B2DPoint( rP1.X(), rP1.Y() ),
            basegfx::B2DPoint( rP2.X(), rP2.Y( ) ), nWidth, nDashing );
}
 
void DrawLine( OutputDevice& rDev, const basegfx::B2DPoint& rP1, const basegfx::B2DPoint& rP2,
    sal_uInt32 nWidth, SvxBorderLineStyle nDashing )
{
    basegfx::B2DPolygon aPolygon;
    aPolygon.append( rP1 );
    aPolygon.append( rP2 );
    lclDrawPolygon( rDev, aPolygon, nWidth, nDashing );
}
 
}
 
static Size gUserItemSz;
static int gFontNameBoxes;
static size_t gPreviewsPerDevice;
static std::vector<VclPtr<VirtualDevice>> gFontPreviewVirDevs;
static std::vector<OUString> gRenderedFontNames;
static int gHighestDPI = 0;
 
namespace
{
    std::vector<VclPtr<VirtualDevice>>& getFontPreviewVirDevs()
    {
        return gFontPreviewVirDevs;
    }
 
    void clearFontPreviewVirDevs()
    {
        for (auto &rDev : gFontPreviewVirDevs)
            rDev.disposeAndClear();
        gFontPreviewVirDevs.clear();
    }
 
    std::vector<OUString>& getRenderedFontNames()
    {
        return gRenderedFontNames;
    }
 
    void clearRenderedFontNames()
    {
        gRenderedFontNames.clear();
    }
 
    void calcCustomItemSize(const weld::ComboBox& rComboBox)
    {
        gUserItemSz = Size(rComboBox.get_approximate_digit_width() * 52, rComboBox.get_text_height());
        gUserItemSz.setHeight(gUserItemSz.Height() * 16);
        gUserItemSz.setHeight(gUserItemSz.Height() / 10);
 
        size_t nMaxDeviceHeight = SAL_MAX_INT16 / 16; // see limitXCreatePixmap and be generous wrt up to x16 hidpi
        assert(gUserItemSz.Height() != 0);
        gPreviewsPerDevice = gUserItemSz.Height() == 0 ? 16 : nMaxDeviceHeight / gUserItemSz.Height();
        if (comphelper::LibreOfficeKit::isActive())
            gPreviewsPerDevice = 1;
    }
}
 
IMPL_LINK(FontNameBox, SettingsChangedHdl, VclSimpleEvent&, rEvent, void)
{
    if (rEvent.GetId() != VclEventId::ApplicationDataChanged)
        return;
 
    if (comphelper::LibreOfficeKit::isActive())
        return;
 
    DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData());
    if (pData->GetType() == DataChangedEventType::SETTINGS)
    {
        clearFontPreviewVirDevs();
        clearRenderedFontNames();
        calcCustomItemSize(*m_xComboBox);
        if (mbWYSIWYG && mpFontList && !mpFontList->empty())
        {
            mnPreviewProgress = 0;
            maUpdateIdle.Start();
        }
    }
}
 
FontNameBox::FontNameBox(std::unique_ptr<weld::ComboBox> p)
    : m_xComboBox(std::move(p))
    , mnPreviewProgress(0)
    , mbWYSIWYG(false)
    , maUpdateIdle("FontNameBox Preview Update")
{
    ++gFontNameBoxes;
    InitFontMRUEntriesFile();
 
    maUpdateIdle.SetPriority(TaskPriority::LOWEST);
    maUpdateIdle.SetInvokeHandler(LINK(this, FontNameBox, UpdateHdl));
 
    Application::AddEventListener(LINK(this, FontNameBox, SettingsChangedHdl));
}
 
FontNameBox::~FontNameBox()
{
    Application::RemoveEventListener(LINK(this, FontNameBox, SettingsChangedHdl));
 
    if (mpFontList)
    {
        SaveMRUEntries (maFontMRUEntriesFile);
        ImplDestroyFontList();
    }
    --gFontNameBoxes;
    if (!gFontNameBoxes)
    {
        clearFontPreviewVirDevs();
        clearRenderedFontNames();
    }
}
 
void FontNameBox::SaveMRUEntries(const OUString& aFontMRUEntriesFile) const
{
    std::vector<OUString> aMRUEntries = m_xComboBox->get_mru_entries();
 
    const sal_Unicode cSep = ';';
    OUStringBuffer aEntries;
    for (size_t i = 0; i < aMRUEntries.size(); i++)
    {
        aEntries.append(aMRUEntries.at(i));
        if (i < aMRUEntries.size() - 1)
            aEntries.append(cSep);
    }
 
    if (aEntries.isEmpty() || aFontMRUEntriesFile.isEmpty())
        return;
 
    SvFileStream aStream;
    aStream.Open( aFontMRUEntriesFile, StreamMode::WRITE | StreamMode::TRUNC );
    if( ! (aStream.IsOpen() && aStream.IsWritable()) )
    {
        SAL_INFO("svtools.control", "FontNameBox::SaveMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed");
        return;
    }
 
    aStream.SetLineDelimiter( LINEEND_LF );
    aStream.WriteLine(aEntries.makeStringAndClear().toUtf8());
    aStream.WriteLine( "" );
}
 
void FontNameBox::LoadMRUEntries( const OUString& aFontMRUEntriesFile )
{
    if (aFontMRUEntriesFile.isEmpty())
        return;
 
    if (!officecfg::Office::Common::Font::View::ShowFontBoxWYSIWYG::get())
        return;
 
    SvFileStream aStream( aFontMRUEntriesFile, StreamMode::READ );
    if( ! aStream.IsOpen() )
    {
        SAL_INFO("svtools.control", "FontNameBox::LoadMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed");
        return;
    }
 
    OStringBuffer aLine;
    aStream.ReadLine( aLine );
    const OUString sEntries = OStringToOUString(aLine, RTL_TEXTENCODING_UTF8);
    std::vector<OUString> aFontEntries;
    const sal_Unicode cSep = ';';
    sal_Int32 nIndex = 0;
    while (nIndex >= 0)
    {
        const OUString sEntry = sEntries.getToken(0, cSep, nIndex);
        if (!sEntry.isEmpty())
            aFontEntries.push_back(sEntry);
    }
 
    m_xComboBox->set_mru_entries(aFontEntries);
}
 
void FontNameBox::InitFontMRUEntriesFile()
{
    OUString sUserConfigDir(u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"_ustr);
    rtl::Bootstrap::expandMacros(sUserConfigDir);
 
    maFontMRUEntriesFile = sUserConfigDir;
    if( !maFontMRUEntriesFile.isEmpty() )
    {
        maFontMRUEntriesFile += FONTNAMEBOXMRUENTRIESFILE;
    }
}
 
void FontNameBox::ImplDestroyFontList()
{
    mpFontList.reset();
    mnPreviewProgress = 0;
    maUpdateIdle.Stop();
}
 
void FontNameBox::Fill( const FontList* pList )
{
    // store old text and clear box
    OUString aOldText = m_xComboBox->get_active_text();
    std::vector<OUString> rEntries = m_xComboBox->get_mru_entries();
    bool bLoadFromFile = rEntries.empty();
    m_xComboBox->freeze();
    m_xComboBox->clear();
 
    ImplDestroyFontList();
    mpFontList.reset(new ImplFontList);
 
    // insert fonts
    size_t nFontCount = pList->GetFontNameCount();
    for (size_t i = 0; i < nFontCount; ++i)
    {
        const FontMetric& rFontMetric = pList->GetFontName(i);
        m_xComboBox->append(OUString::number(i), rFontMetric.GetFamilyName());
        mpFontList->push_back(rFontMetric);
    }
 
    if (bLoadFromFile)
        LoadMRUEntries(maFontMRUEntriesFile);
    else
        m_xComboBox->set_mru_entries(rEntries);
 
    m_xComboBox->thaw();
 
    if (mbWYSIWYG && nFontCount)
    {
        assert(mnPreviewProgress == 0 && "ImplDestroyFontList wasn't called");
        maUpdateIdle.Start();
    }
 
    // restore text
    if (!aOldText.isEmpty())
        set_active_or_entry_text(aOldText);
}
 
void FontNameBox::EnableWYSIWYG(bool bEnable)
{
    if (o3tl::IsRunningUnitTest())
        return;
    if (mbWYSIWYG == bEnable)
        return;
    mbWYSIWYG = bEnable;
 
    if (mbWYSIWYG)
    {
        calcCustomItemSize(*m_xComboBox);
        m_xComboBox->connect_custom_get_size(LINK(this, FontNameBox, CustomGetSizeHdl));
        m_xComboBox->connect_custom_render(LINK(this, FontNameBox, CustomRenderHdl));
    }
    else
    {
        m_xComboBox->connect_custom_get_size(Link<OutputDevice&, Size>());
        m_xComboBox->connect_custom_render(Link<weld::ComboBox::render_args, void>());
    }
    m_xComboBox->set_custom_renderer(mbWYSIWYG);
}
 
IMPL_LINK(FontNameBox, CustomGetSizeHdl, OutputDevice&, rDevice, Size)
{
    if (comphelper::LibreOfficeKit::isActive())
    {
        calcCustomItemSize(*m_xComboBox);
        gUserItemSz.setWidth(1.0 * rDevice.GetDPIX() / 96.0 * gUserItemSz.getWidth());
        gUserItemSz.setHeight(1.0 * rDevice.GetDPIY() / 96.0 * gUserItemSz.getHeight());
    }
    return mbWYSIWYG ? gUserItemSz : Size();
}
 
namespace
{
    tools::Long shrinkFontToFit(OUString const &rSampleText, tools::Long nH, vcl::Font &rFont, OutputDevice &rDevice, tools::Rectangle &rTextRect)
    {
        tools::Long nWidth = 0;
 
        Size aSize( rFont.GetFontSize() );
 
        //Make sure it fits in the available height
        while (aSize.Height() > 0)
        {
            if (!rDevice.GetTextBoundRect(rTextRect, rSampleText))
                break;
            if (rTextRect.GetHeight() <= nH)
            {
                nWidth = rTextRect.GetWidth();
                break;
            }
 
            aSize.AdjustHeight( -(EXTRAFONTSIZE) );
            rFont.SetFontSize(aSize);
            rDevice.SetFont(rFont);
        }
 
        return nWidth;
    }
}
 
IMPL_LINK_NOARG(FontNameBox, UpdateHdl, Timer*, void)
{
    if (comphelper::LibreOfficeKit::isActive())
        return;
 
    CachePreview(mnPreviewProgress++, nullptr);
    // tdf#132536 limit to ~25 pre-rendered for now. The font caches look
    // b0rked, the massive charmaps are ~never swapped out, and don't count
    // towards the size of a font in the font cache.
    if (mnPreviewProgress < std::min<size_t>(25, mpFontList->size()))
        maUpdateIdle.Start();
}
 
static void DrawPreview(const FontMetric& rFontMetric, const Point& rTopLeft, OutputDevice& rDevice, bool bSelected)
{
    auto popIt = rDevice.ScopedPush(vcl::PushFlags::TEXTCOLOR);
 
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    if (bSelected)
        rDevice.SetTextColor(rStyleSettings.GetHighlightTextColor());
    else
        rDevice.SetTextColor(rStyleSettings.GetDialogTextColor());
 
    tools::Long nX = rTopLeft.X();
    tools::Long nH = gUserItemSz.Height();
 
    nX += IMGOUTERTEXTSPACE;
 
    const bool bSymbolFont = isSymbolFont(rFontMetric);
 
    vcl::Font aOldFont(rDevice.GetFont());
    Size aSize( aOldFont.GetFontSize() );
    aSize.AdjustHeight(EXTRAFONTSIZE );
    vcl::Font aFont( rFontMetric );
    aFont.SetFontSize( aSize );
    rDevice.SetFont(aFont);
 
    bool bUsingCorrectFont = true;
    tools::Rectangle aTextRect;
 
    // Preview the font name
    const OUString& sFontName = rFontMetric.GetFamilyName();
 
    //If it shouldn't or can't draw its own name because it doesn't have the glyphs
    if (!canRenderNameOfSelectedFont(rDevice))
        bUsingCorrectFont = false;
    else
    {
        //Make sure it fits in the available height, shrinking the font if necessary
        bUsingCorrectFont = shrinkFontToFit(sFontName, nH, aFont, rDevice, aTextRect) != 0;
    }
 
    if (!bUsingCorrectFont)
    {
        rDevice.SetFont(aOldFont);
        rDevice.GetTextBoundRect(aTextRect, sFontName);
    }
 
    tools::Long nTextHeight = aTextRect.GetHeight();
    tools::Long nDesiredGap = (nH-nTextHeight)/2;
    tools::Long nVertAdjust = nDesiredGap - aTextRect.Top();
    Point aPos( nX, rTopLeft.Y() + nVertAdjust );
    rDevice.DrawText(aPos, sFontName);
    tools::Long nTextX = aPos.X() + aTextRect.GetWidth() + GAPTOEXTRAPREVIEW;
 
    if (!bUsingCorrectFont)
        rDevice.SetFont(aFont);
 
    OUString sSampleText;
 
    if (!bSymbolFont)
    {
        const bool bNameBeginsWithLatinText = rFontMetric.GetFamilyName()[0] <= 'z';
 
        if (bNameBeginsWithLatinText || !bUsingCorrectFont)
            sSampleText = makeShortRepresentativeTextForSelectedFont(rDevice);
    }
 
    //If we're not a symbol font, but could neither render our own name and
    //we can't determine what script it would like to render, then try a
    //few well known scripts
    if (sSampleText.isEmpty() && !bUsingCorrectFont)
    {
        static const UScriptCode aScripts[] =
        {
            USCRIPT_ARABIC,
            USCRIPT_HEBREW,
 
            USCRIPT_BENGALI,
            USCRIPT_GURMUKHI,
            USCRIPT_GUJARATI,
            USCRIPT_ORIYA,
            USCRIPT_TAMIL,
            USCRIPT_TELUGU,
            USCRIPT_KANNADA,
            USCRIPT_MALAYALAM,
            USCRIPT_SINHALA,
            USCRIPT_DEVANAGARI,
 
            USCRIPT_THAI,
            USCRIPT_LAO,
            USCRIPT_GEORGIAN,
            USCRIPT_TIBETAN,
            USCRIPT_SYRIAC,
            USCRIPT_MYANMAR,
            USCRIPT_ETHIOPIC,
            USCRIPT_KHMER,
            USCRIPT_MONGOLIAN,
 
            USCRIPT_KOREAN,
            USCRIPT_JAPANESE,
            USCRIPT_HAN,
            USCRIPT_SIMPLIFIED_HAN,
            USCRIPT_TRADITIONAL_HAN,
 
            USCRIPT_GREEK
        };
 
        for (const UScriptCode& rScript : aScripts)
        {
            OUString sText = makeShortRepresentativeTextForScript(rScript);
            if (!sText.isEmpty())
            {
                bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText));
                if (bHasSampleTextGlyphs)
                {
                    sSampleText = sText;
                    break;
                }
            }
        }
 
        static const UScriptCode aMinimalScripts[] =
        {
            USCRIPT_HEBREW, //e.g. biblical hebrew
            USCRIPT_GREEK
        };
 
        for (const UScriptCode& rMinimalScript : aMinimalScripts)
        {
            OUString sText = makeShortMinimalTextForScript(rMinimalScript);
            if (!sText.isEmpty())
            {
                bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText));
                if (bHasSampleTextGlyphs)
                {
                    sSampleText = sText;
                    break;
                }
            }
        }
    }
 
    //If we're a symbol font, or for some reason the font still couldn't
    //render something representative of what it would like to render then
    //make up some semi-random text that it *can* display
    if (bSymbolFont || (!bUsingCorrectFont && sSampleText.isEmpty()))
        sSampleText = makeShortRepresentativeSymbolTextForSelectedFont(rDevice);
 
    if (!sSampleText.isEmpty())
    {
        const Size &rItemSize = gUserItemSz;
 
        //leave a little border at the edge
        tools::Long nSpace = rItemSize.Width() - nTextX - IMGOUTERTEXTSPACE;
        if (nSpace >= 0)
        {
            //Make sure it fits in the available height, and get how wide that would be
            tools::Long nWidth = shrinkFontToFit(sSampleText, nH, aFont, rDevice, aTextRect);
            //Chop letters off until it fits in the available width
            while (nWidth > nSpace || nWidth > gUserItemSz.Width())
            {
                sSampleText = sSampleText.copy(0, sSampleText.getLength()-1);
                nWidth = rDevice.GetTextBoundRect(aTextRect, sSampleText) ?
                         aTextRect.GetWidth() : 0;
            }
 
            //center the text on the line
            if (!sSampleText.isEmpty() && nWidth)
            {
                nTextHeight = aTextRect.GetHeight();
                nDesiredGap = (nH-nTextHeight)/2;
                nVertAdjust = nDesiredGap - aTextRect.Top();
                aPos = Point(nTextX + nSpace - nWidth, rTopLeft.Y() + nVertAdjust);
                rDevice.DrawText(aPos, sSampleText);
            }
        }
    }
 
    rDevice.SetFont(aOldFont);
}
 
OutputDevice& FontNameBox::CachePreview(size_t nIndex, Point* pTopLeft,
                                        sal_Int32 nDPIX, sal_Int32 nDPIY)
{
    SolarMutexGuard aGuard;
    const FontMetric& rFontMetric = (*mpFontList)[nIndex];
    const OUString& rFontName = rFontMetric.GetFamilyName();
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        // we want to cache only best quality previews
        if (gHighestDPI < nDPIX || gHighestDPI < nDPIY)
        {
            clearRenderedFontNames();
            clearFontPreviewVirDevs();
            gHighestDPI = std::max(nDPIX, nDPIY);
        }
        else if (gHighestDPI > nDPIX || gHighestDPI > nDPIY)
        {
            nDPIX = gHighestDPI;
            nDPIY = gHighestDPI;
        }
    }
 
    size_t nPreviewIndex;
    auto& rFontNames = getRenderedFontNames();
    auto& rVirtualDevs = getFontPreviewVirDevs();
    auto xFind = std::find(rFontNames.begin(), rFontNames.end(), rFontName);
    bool bPreviewAvailable = xFind != rFontNames.end();
    if (!bPreviewAvailable)
    {
        nPreviewIndex = rFontNames.size();
        rFontNames.push_back(rFontName);
    }
    else
        nPreviewIndex = std::distance(rFontNames.begin(), xFind);
 
    size_t nPage = nPreviewIndex / gPreviewsPerDevice;
    size_t nIndexInPage = nPreviewIndex - (nPage * gPreviewsPerDevice);
 
    Point aTopLeft(0, gUserItemSz.Height() * nIndexInPage);
 
    if (!bPreviewAvailable)
    {
        if (nPage >= rVirtualDevs.size())
        {
            bool bIsLOK = comphelper::LibreOfficeKit::isActive();
            rVirtualDevs.emplace_back(VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA));
 
            VirtualDevice& rDevice = *rVirtualDevs.back();
            rDevice.SetOutputSizePixel(Size(gUserItemSz.Width(), gUserItemSz.Height() * gPreviewsPerDevice));
            const Color aColor = Application::GetSettings().GetStyleSettings().GetFieldColor();
            rDevice.SetBackground(Wallpaper(aColor));
            rDevice.Erase();
            if (bIsLOK)
            {
                rDevice.SetDPIX(nDPIX);
                rDevice.SetDPIY(nDPIY);
            }
 
            weld::SetPointFont(rDevice, m_xComboBox->get_font(), bIsLOK);
            assert(rVirtualDevs.size() == nPage + 1);
        }
 
        DrawPreview(rFontMetric, aTopLeft, *rVirtualDevs.back(), false);
    }
 
    if (pTopLeft)
        *pTopLeft = aTopLeft;
 
    return *rVirtualDevs[nPage];
}
 
IMPL_LINK(FontNameBox, CustomRenderHdl, weld::ComboBox::render_args, aPayload, void)
{
    vcl::RenderContext& rRenderContext = std::get<0>(aPayload);
    const ::tools::Rectangle& rRect = std::get<1>(aPayload);
    bool bSelected = std::get<2>(aPayload);
    const OUString& rId = std::get<3>(aPayload);
 
    sal_uInt32 nIndex = rId.toUInt32();
 
    Point aDestPoint(rRect.TopLeft());
    auto nMargin = (rRect.GetHeight() - gUserItemSz.Height()) / 2;
    aDestPoint.AdjustY(nMargin);
 
    if (bSelected)
    {
        const FontMetric& rFontMetric = (*mpFontList)[nIndex];
        DrawPreview(rFontMetric, aDestPoint, rRenderContext, true);
        m_aLivePreviewHdl.Call(rFontMetric);
    }
    else
    {
        // use cache of unselected entries
        Point aTopLeft;
        OutputDevice& rDevice = CachePreview(nIndex, &aTopLeft,
                                             rRenderContext.GetDPIX(),
                                             rRenderContext.GetDPIY());
 
        Size aSourceSize = comphelper::LibreOfficeKit::isActive() ? rDevice.GetOutputSizePixel() : gUserItemSz;
        rRenderContext.DrawOutDev(aDestPoint, gUserItemSz,
                                  aTopLeft, aSourceSize,
                                  rDevice);
    }
}
 
void FontNameBox::set_active_or_entry_text(const OUString& rText)
{
    const int nFound = m_xComboBox->find_text(rText);
    if (nFound != -1)
        m_xComboBox->set_active(nFound);
    m_xComboBox->set_entry_text(rText);
}
 
FontStyleBox::FontStyleBox(std::unique_ptr<weld::ComboBox> p)
    : m_xComboBox(std::move(p))
    , m_aLastStyle(m_xComboBox->get_active_text())
{
    //Use the standard texts to get an optimal size and stick to that size.
    //That should stop the character dialog dancing around.
    auto nMaxLen = m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT)).Width();
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT_ITALIC)).Width());
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL)).Width());
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL_ITALIC)).Width());
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD)).Width());
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD_ITALIC)).Width());
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK)).Width());
    nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK_ITALIC)).Width());
    m_xComboBox->set_entry_width_chars(std::ceil(nMaxLen / m_xComboBox->get_approximate_digit_width()));
    m_xComboBox->connect_changed(LINK(this, FontStyleBox, ChangeHdl));
}
 
IMPL_LINK(FontStyleBox, ChangeHdl, weld::ComboBox&, rComboBox, void)
{
    // update m_aLastStyle to whatever is explicitly selected by the user
    m_aLastStyle = rComboBox.get_active_text();
    m_aChangedLink.Call(rComboBox);
}
 
void FontStyleBox::Fill( std::u16string_view rName, const FontList* pList )
{
    OUString aOldText = m_xComboBox->get_active_text();
 
    m_xComboBox->freeze();
    m_xComboBox->clear();
 
    // does a font with this name already exist?
    sal_Handle hFontMetric = pList->GetFirstFontMetric( rName );
    if ( hFontMetric )
    {
        OUString aStyleText;
        FontWeight  eLastWeight = WEIGHT_DONTKNOW;
        FontItalic  eLastItalic = ITALIC_NONE;
        FontWidth   eLastWidth = WIDTH_DONTKNOW;
        bool        bNormal = false;
        bool        bItalic = false;
        bool        bBold = false;
        bool        bBoldItalic = false;
        bool        bInsert = false;
        FontMetric    aFontMetric;
        while ( hFontMetric )
        {
            aFontMetric = FontList::GetFontMetric( hFontMetric );
 
            FontWeight  eWeight = aFontMetric.GetWeightMaybeAskConfig();
            FontItalic  eItalic = aFontMetric.GetItalicMaybeAskConfig();
            FontWidth   eWidth = aFontMetric.GetWidthTypeMaybeAskConfig();
            // Only if the attributes are different, we insert the
            // Font to avoid double Entries in different languages
            if ( (eWeight != eLastWeight) || (eItalic != eLastItalic) ||
                 (eWidth != eLastWidth) )
            {
                if ( bInsert )
                    m_xComboBox->append_text(aStyleText);
 
                if ( eWeight <= WEIGHT_NORMAL )
                {
                    if ( eItalic != ITALIC_NONE )
                        bItalic = true;
                    else
                        bNormal = true;
                }
                else
                {
                    if ( eItalic != ITALIC_NONE )
                        bBoldItalic = true;
                    else
                        bBold = true;
                }
 
                // For wrong StyleNames we replace this with the correct once
                aStyleText = pList->GetStyleName( aFontMetric );
                bInsert = m_xComboBox->find_text(aStyleText) == -1;
                if ( !bInsert )
                {
                    aStyleText = pList->GetStyleName( eWeight, eItalic );
                    bInsert = m_xComboBox->find_text(aStyleText) == -1;
                }
 
                eLastWeight = eWeight;
                eLastItalic = eItalic;
                eLastWidth = eWidth;
            }
            else
            {
                if ( bInsert )
                {
                    // If we have two names for the same attributes
                    // we prefer the translated standard names
                    const OUString& rAttrStyleText = pList->GetStyleName( eWeight, eItalic );
                    if (rAttrStyleText != aStyleText)
                    {
                        OUString aTempStyleText = pList->GetStyleName( aFontMetric );
                        if (rAttrStyleText == aTempStyleText)
                            aStyleText = rAttrStyleText;
                        bInsert = m_xComboBox->find_text(aStyleText) == -1;
                    }
                }
            }
 
            if ( !bItalic && (aStyleText == pList->GetItalicStr()) )
                bItalic = true;
            else if ( !bBold && (aStyleText == pList->GetBoldStr()) )
                bBold = true;
            else if ( !bBoldItalic && (aStyleText == pList->GetBoldItalicStr()) )
                bBoldItalic = true;
 
            hFontMetric = FontList::GetNextFontMetric( hFontMetric );
        }
 
        if ( bInsert )
            m_xComboBox->append_text(aStyleText);
 
        // certain style as copy
        if ( bNormal )
        {
            if ( !bItalic )
                m_xComboBox->append_text(pList->GetItalicStr());
            if ( !bBold )
                m_xComboBox->append_text(pList->GetBoldStr());
        }
        if ( !bBoldItalic )
        {
            if ( bNormal || bItalic || bBold )
                m_xComboBox->append_text(pList->GetBoldItalicStr());
        }
    }
    else
    {
        // insert standard styles if no font
        m_xComboBox->append_text(pList->GetNormalStr());
        m_xComboBox->append_text(pList->GetItalicStr());
        m_xComboBox->append_text(pList->GetBoldStr());
        m_xComboBox->append_text(pList->GetBoldItalicStr());
    }
 
    m_xComboBox->thaw();
 
    // tdf#162113 prefer restoring the last explicitly set
    // style if that is possible
    if (!m_aLastStyle.isEmpty())
    {
        int nFound = m_xComboBox->find_text(m_aLastStyle);
        if (nFound != -1)
        {
            m_xComboBox->set_active(nFound);
            return;
        }
    }
 
    // otherwise, restore the style that was last selected
    // if that is possible
    if (!aOldText.isEmpty())
    {
        int nFound = m_xComboBox->find_text(aOldText);
        if (nFound != -1)
            m_xComboBox->set_active(nFound);
        else
        {
            // otherwise, just pick something
            m_xComboBox->set_active(0);
        }
    }
 
    // tdf#165265 if nothing was originally selected, then
    // don't pick anything and leave the combobox empty
    // so the search for style feature doesn't add any
    // not-explicitly selected font styles by default
}
 
FontSizeBox::FontSizeBox(std::unique_ptr<weld::ComboBox> p)
    : pFontList(nullptr)
    , nSavedValue(0)
    , nMin(20)
    , nMax(9999)
    , eUnit(FieldUnit::POINT)
    , nDecimalDigits(1)
    , nRelMin(0)
    , nRelMax(0)
    , nRelStep(0)
    , nPtRelMin(0)
    , nPtRelMax(0)
    , nPtRelStep(0)
    , bRelativeMode(false)
    , bRelative(false)
    , bPtRelative(false)
    , bStdSize(false)
    , m_xComboBox(std::move(p))
{
    m_xComboBox->set_entry_width_chars(std::ceil(m_xComboBox->get_pixel_size(format_number(105)).Width() /
                                                 m_xComboBox->get_approximate_digit_width()));
    m_xComboBox->connect_focus_out(LINK(this, FontSizeBox, ReformatHdl));
    m_xComboBox->connect_changed(LINK(this, FontSizeBox, ModifyHdl));
}
 
void FontSizeBox::set_active_or_entry_text(const OUString& rText)
{
    const int nFound = m_xComboBox->find_text(rText);
    if (nFound != -1)
        m_xComboBox->set_active(nFound);
    m_xComboBox->set_entry_text(rText);
}
 
IMPL_LINK(FontSizeBox, ReformatHdl, weld::Widget&, rWidget, void)
{
    FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType());
    if (!bRelativeMode || !aFontSizeNames.IsEmpty())
    {
        if (aFontSizeNames.Name2Size(m_xComboBox->get_active_text()) != 0)
            return;
    }
 
    set_value(get_value());
 
    m_aFocusOutHdl.Call(rWidget);
}
 
IMPL_LINK(FontSizeBox, ModifyHdl, weld::ComboBox&, rBox, void)
{
    if (bRelativeMode)
    {
        OUString aStr = comphelper::string::stripStart(rBox.get_active_text(), ' ');
 
        bool bNewMode = bRelative;
        bool bOldPtRelMode = bPtRelative;
 
        if ( bRelative )
        {
            bPtRelative = false;
            const sal_Unicode* pStr = aStr.getStr();
            while ( *pStr )
            {
                if ( ((*pStr < '0') || (*pStr > '9')) && (*pStr != '%') && !unicode::isSpace(*pStr) )
                {
                    if ( ('-' == *pStr || '+' == *pStr) && !bPtRelative )
                        bPtRelative = true;
                    else if ( bPtRelative && 'p' == *pStr && 't' == *++pStr )
                        ;
                    else
                    {
                        bNewMode = false;
                        break;
                    }
                }
                pStr++;
            }
        }
        else if (!aStr.isEmpty())
        {
            if ( -1 != aStr.indexOf('%') )
            {
                bNewMode = true;
                bPtRelative = false;
            }
 
            if ( '-' == aStr[0] || '+' == aStr[0] )
            {
                bNewMode = true;
                bPtRelative = true;
            }
        }
 
        if ( bNewMode != bRelative || bPtRelative != bOldPtRelMode )
            SetRelative( bNewMode );
    }
    m_aChangeHdl.Call(rBox);
}
 
void FontSizeBox::Fill( const FontList* pList )
{
    // remember for relative mode
    pFontList = pList;
 
    // no font sizes need to be set for relative mode
    if ( bRelative )
        return;
 
    // query font sizes
    const int* pTempAry;
    const int* pAry = nullptr;
 
    pAry = FontList::GetStdSizeAry();
 
    // first insert font size names (for simplified/traditional chinese)
    FontSizeNames aFontSizeNames( Application::GetSettings().GetUILanguageTag().getLanguageType() );
    if ( pAry == FontList::GetStdSizeAry() )
    {
        // for standard sizes we don't need to bother
        if (bStdSize && m_xComboBox->get_count() && aFontSizeNames.IsEmpty())
            return;
        bStdSize = true;
    }
    else
        bStdSize = false;
 
    int nSelectionStart, nSelectionEnd;
    m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd);
    OUString aStr = m_xComboBox->get_active_text();
 
    m_xComboBox->freeze();
    m_xComboBox->clear();
    int nPos = 0;
 
    if ( !aFontSizeNames.IsEmpty() )
    {
        if ( pAry == FontList::GetStdSizeAry() )
        {
            // for scalable fonts all font size names
            sal_uInt32 nCount = aFontSizeNames.Count();
            for( sal_uInt32 i = 0; i < nCount; i++ )
            {
                OUString aSizeName = aFontSizeNames.GetIndexName( i );
                int nSize = aFontSizeNames.GetIndexSize( i );
                OUString sId(OUString::number(-nSize)); // mark as special
                m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr);
                nPos++;
            }
        }
        else
        {
            // for fixed size fonts only selectable font size names
            pTempAry = pAry;
            while ( *pTempAry )
            {
                OUString aSizeName = aFontSizeNames.Size2Name( *pTempAry );
                if ( !aSizeName.isEmpty() )
                {
                    OUString sId(OUString::number(-(*pTempAry))); // mark as special
                    m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr);
                    nPos++;
                }
                pTempAry++;
            }
        }
    }
 
    // then insert numerical font size values
    pTempAry = pAry;
    while (*pTempAry)
    {
        InsertValue(*pTempAry);
        ++pTempAry;
    }
 
    m_xComboBox->thaw();
    set_active_or_entry_text(aStr);
    m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd);
}
 
void FontSizeBox::EnableRelativeMode( sal_uInt16 nNewMin, sal_uInt16 nNewMax, sal_uInt16 nStep )
{
    bRelativeMode = true;
    nRelMin       = nNewMin;
    nRelMax       = nNewMax;
    nRelStep      = nStep;
    SetUnit(FieldUnit::POINT);
}
 
void FontSizeBox::EnablePtRelativeMode( short nNewMin, short nNewMax, short nStep )
{
    bRelativeMode = true;
    nPtRelMin     = nNewMin;
    nPtRelMax     = nNewMax;
    nPtRelStep    = nStep;
    SetUnit(FieldUnit::POINT);
}
 
void FontSizeBox::InsertValue(int i)
{
    OUString sNumber(OUString::number(i));
    m_xComboBox->append(sNumber, format_number(i));
}
 
void FontSizeBox::SetRelative( bool bNewRelative )
{
    if ( !bRelativeMode )
        return;
 
    int nSelectionStart, nSelectionEnd;
    m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd);
    OUString aStr = comphelper::string::stripStart(m_xComboBox->get_active_text(), ' ');
 
    if (bNewRelative)
    {
        bRelative = true;
        bStdSize = false;
 
        m_xComboBox->clear();
 
        if (bPtRelative)
        {
            SetDecimalDigits( 1 );
            SetRange(nPtRelMin, nPtRelMax);
            SetUnit(FieldUnit::POINT);
 
            short i = nPtRelMin, n = 0;
            // JP 30.06.98: more than 100 values are not useful
            while ( i <= nPtRelMax && n++ < 100 )
            {
                InsertValue( i );
                i = i + nPtRelStep;
            }
        }
        else
        {
            SetDecimalDigits(0);
            SetRange(nRelMin, nRelMax);
            SetUnit(FieldUnit::PERCENT);
 
            sal_uInt16 i = nRelMin;
            while ( i <= nRelMax )
            {
                InsertValue( i );
                i = i + nRelStep;
            }
        }
    }
    else
    {
        if (pFontList)
            m_xComboBox->clear();
        bRelative = bPtRelative = false;
        SetDecimalDigits(1);
        SetRange(20, 9999);
        SetUnit(FieldUnit::POINT);
        if ( pFontList)
            Fill( pFontList );
    }
 
    set_active_or_entry_text(aStr);
    m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd);
}
 
OUString FontSizeBox::format_number(int nValue) const
{
    OUString sRet;
 
    //pawn percent off to icu to decide whether percent is separated from its number for this locale
    if (eUnit == FieldUnit::PERCENT)
    {
        double fValue = nValue;
        fValue /= weld::SpinButton::Power10(nDecimalDigits);
        sRet = unicode::formatPercent(fValue, Application::GetSettings().GetUILanguageTag());
    }
    else
    {
        const SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
        sRet = rLocaleData.getNum(nValue, nDecimalDigits, true, false);
        if (eUnit != FieldUnit::NONE && eUnit != FieldUnit::DEGREE)
            sRet += " ";
        assert(eUnit != FieldUnit::PERCENT);
        sRet += weld::MetricSpinButton::MetricToString(eUnit);
    }
 
    if (bRelativeMode && bPtRelative && (0 <= nValue) && !sRet.isEmpty())
        sRet = "+" + sRet;
 
    return sRet;
}
 
void FontSizeBox::SetValue(int nNewValue, FieldUnit eInUnit)
{
    auto nTempValue = vcl::ConvertValue(nNewValue, 0, GetDecimalDigits(), eInUnit, GetUnit());
    if (nTempValue < nMin)
        nTempValue = nMin;
    else if (nTempValue > nMax)
        nTempValue = nMax;
    if (!bRelative)
    {
        FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType());
        // conversion loses precision; however font sizes should
        // never have a problem with that
        OUString aName = aFontSizeNames.Size2Name(nTempValue);
        if (!aName.isEmpty() && m_xComboBox->find_text(aName) != -1)
        {
            m_xComboBox->set_active_text(aName);
            return;
        }
    }
    OUString aResult = format_number(nTempValue);
    set_active_or_entry_text(aResult);
}
 
void FontSizeBox::set_value(int nNewValue)
{
    SetValue(nNewValue, eUnit);
}
 
int FontSizeBox::get_value() const
{
    OUString aStr = m_xComboBox->get_active_text();
    if (!bRelative)
    {
        FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType());
        auto nValue = aFontSizeNames.Name2Size(aStr);
        if (nValue)
        {
            sal_Int64 ret = vcl::ConvertValue(nValue, 0, GetDecimalDigits(), GetUnit(), GetUnit());
            o3tl::untaint_for_overrun(ret);
            return ret;
        }
    }
 
    const SvtSysLocale aSysLocale;
    const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
    double fResult(0.0);
    (void)vcl::TextToValue(aStr, fResult, 0, GetDecimalDigits(), rLocaleData, GetUnit());
    if (!aStr.isEmpty())
    {
        if (fResult < nMin)
            fResult = nMin;
        else if (fResult > nMax)
            fResult = nMax;
    }
    return std::round(fResult);
}
 
SvxBorderLineStyle SvtLineListBox::GetSelectEntryStyle() const
{
    if (m_xLineSet->IsNoSelection())
        return SvxBorderLineStyle::NONE;
    auto nId = m_xLineSet->GetSelectedItemId();
    return static_cast<SvxBorderLineStyle>(nId - 1);
}
 
namespace
{
    Size getPreviewSize(const weld::Widget& rControl)
    {
        return Size(rControl.get_approximate_digit_width() * 15, rControl.get_text_height());
    }
}
 
void SvtLineListBox::ImpGetLine( tools::Long nLine1, tools::Long nLine2, tools::Long nDistance,
                            Color aColor1, Color aColor2, Color aColorDist,
                            SvxBorderLineStyle nStyle, Bitmap& rBmp )
{
    Size aSize(getPreviewSize(*m_xControl));
 
    // SourceUnit to Twips
    if ( eSourceUnit == FieldUnit::POINT )
    {
        nLine1      /= 5;
        nLine2      /= 5;
        nDistance   /= 5;
    }
 
    // Paint the lines
    aSize = aVirDev->PixelToLogic( aSize );
    tools::Long nPix = aVirDev->PixelToLogic( Size( 0, 1 ) ).Height();
    sal_uInt32 n1 = nLine1;
    sal_uInt32 n2 = nLine2;
    tools::Long nDist  = nDistance;
    n1 += nPix-1;
    n1 -= n1%nPix;
    if ( n2 )
    {
        nDist += nPix-1;
        nDist -= nDist%nPix;
        n2    += nPix-1;
        n2    -= n2%nPix;
    }
    tools::Long nVirHeight = n1+nDist+n2;
    if ( nVirHeight > aSize.Height() )
        aSize.setHeight( nVirHeight );
    // negative width should not be drawn
    if ( aSize.Width() <= 0 )
        return;
 
    Size aVirSize = aVirDev->LogicToPixel( aSize );
    if ( aVirDev->GetOutputSizePixel() != aVirSize )
        aVirDev->SetOutputSizePixel( aVirSize );
    aVirDev->SetFillColor( aColorDist );
    aVirDev->DrawRect( tools::Rectangle( Point(), aSize ) );
 
    aVirDev->SetFillColor( aColor1 );
 
    double y1 = double( n1 ) / 2;
    svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y1 ), basegfx::B2DPoint( aSize.Width( ), y1 ), n1, nStyle );
 
    if ( n2 )
    {
        double y2 =  n1 + nDist + double( n2 ) / 2;
        aVirDev->SetFillColor( aColor2 );
        svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y2 ), basegfx::B2DPoint( aSize.Width(), y2 ), n2, SvxBorderLineStyle::SOLID );
    }
    rBmp = aVirDev->GetBitmap( Point(), Size( aSize.Width(), n1+nDist+n2 ) );
}
 
SvtLineListBox::SvtLineListBox(std::unique_ptr<weld::MenuButton> pControl)
    : WeldToolbarPopup(css::uno::Reference<css::frame::XFrame>(), pControl.get(), u"svt/ui/linewindow.ui"_ustr, u"line_popup_window"_ustr)
    , m_xControl(std::move(pControl))
    , m_xNoneButton(m_xBuilder->weld_button(u"none_line_button"_ustr))
    , m_xLineSet(new ValueSet(nullptr))
    , m_xLineSetWin(new weld::CustomWeld(*m_xBuilder, u"lineset"_ustr, *m_xLineSet))
    , m_nWidth( 5 )
    , aVirDev(VclPtr<VirtualDevice>::Create())
    , aColor(COL_BLACK)
{
    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    m_xLineSet->SetStyle(WinBits(WB_FLATVALUESET | WB_NO_DIRECTSELECT | WB_TABSTOP));
    m_xLineSet->SetItemHeight(rStyleSettings.GetListBoxPreviewDefaultPixelSize().Height() + 1);
    m_xLineSet->SetColCount(1);
    m_xLineSet->SetSelectHdl(LINK(this, SvtLineListBox, ValueSelectHdl));
 
    m_xNoneButton->connect_clicked(LINK(this, SvtLineListBox, NoneHdl));
 
    m_xControl->set_popover(m_xTopLevel.get());
    m_xControl->connect_toggled(LINK(this, SvtLineListBox, ToggleHdl));
    m_xControl->connect_style_updated(LINK(this, SvtLineListBox, StyleUpdatedHdl));
 
    // lock size to these maxes height/width so it doesn't jump around in size
    m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE));
    Size aNonePrefSize = m_xControl->get_preferred_size();
    m_xControl->set_label(u""_ustr);
    aVirDev->SetOutputSizePixel(getPreviewSize(*m_xControl));
    m_xControl->set_image(aVirDev);
    Size aSolidPrefSize = m_xControl->get_preferred_size();
    m_xControl->set_size_request(std::max(aNonePrefSize.Width(), aSolidPrefSize.Width()),
                                 std::max(aNonePrefSize.Height(), aSolidPrefSize.Height()));
 
    eSourceUnit = FieldUnit::POINT;
 
    aVirDev->SetLineColor();
    aVirDev->SetMapMode(MapMode(MapUnit::MapTwip));
}
 
void SvtLineListBox::GrabFocus()
{
    if (GetSelectEntryStyle() == SvxBorderLineStyle::NONE)
        m_xNoneButton->grab_focus();
    else
        m_xLineSet->GrabFocus();
}
 
IMPL_LINK(SvtLineListBox, ToggleHdl, weld::Toggleable&, rButton, void)
{
    if (rButton.get_active())
        GrabFocus();
}
 
IMPL_LINK_NOARG(SvtLineListBox, StyleUpdatedHdl, weld::Widget&, void)
{
    UpdateEntries();
    UpdatePreview();
}
 
IMPL_LINK_NOARG(SvtLineListBox, NoneHdl, weld::Button&, void)
{
    SelectEntry(SvxBorderLineStyle::NONE);
    ValueSelectHdl(nullptr);
}
 
SvtLineListBox::~SvtLineListBox()
{
}
 
OUString SvtLineListBox::GetLineStyleName(SvxBorderLineStyle eStyle)
{
    OUString sRet;
    for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i)
    {
        if (eStyle == RID_SVXSTR_BORDERLINE[i].second)
        {
            sRet = SvtResId(RID_SVXSTR_BORDERLINE[i].first);
            break;
        }
    }
    return sRet;
}
 
void SvtLineListBox::SelectEntry(SvxBorderLineStyle nStyle)
{
    if (nStyle == SvxBorderLineStyle::NONE)
        m_xLineSet->SetNoSelection();
    else
        m_xLineSet->SelectItem(static_cast<sal_Int16>(nStyle) + 1);
    UpdatePreview();
}
 
void SvtLineListBox::InsertEntry(
    const BorderWidthImpl& rWidthImpl, SvxBorderLineStyle nStyle, tools::Long nMinWidth,
    ColorFunc pColor1Fn, ColorFunc pColor2Fn, ColorDistFunc pColorDistFn )
{
    m_vLineList.emplace_back(new ImpLineListData(
        rWidthImpl, nStyle, nMinWidth, pColor1Fn, pColor2Fn, pColorDistFn));
}
 
void SvtLineListBox::UpdateEntries()
{
    SvxBorderLineStyle eSelected = GetSelectEntryStyle();
 
    // Remove the old entries
    m_xLineSet->Clear();
 
    const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
    Color aFieldColor = rSettings.GetFieldColor();
 
    // Add the new entries based on the defined width
    sal_uInt16 n = 0;
    sal_uInt16 nCount = m_vLineList.size( );
    while ( n < nCount )
    {
        auto& pData = m_vLineList[ n ];
        Bitmap aBmp;
        ImpGetLine( pData->GetLine1ForWidth( m_nWidth ),
                pData->GetLine2ForWidth( m_nWidth ),
                pData->GetDistForWidth( m_nWidth ),
                pData->GetColorLine1(aColor),
                pData->GetColorLine2(aColor),
                pData->GetColorDist(aColor, aFieldColor),
                pData->GetStyle(), aBmp );
        sal_Int16 nItemId = static_cast<sal_Int16>(pData->GetStyle()) + 1;
        m_xLineSet->InsertItem(nItemId, Image(aBmp), GetLineStyleName(pData->GetStyle()));
        if (pData->GetStyle() == eSelected)
            m_xLineSet->SelectItem(nItemId);
        n++;
    }
 
    m_xLineSet->SetOptimalSize();
}
 
IMPL_LINK_NOARG(SvtLineListBox, ValueSelectHdl, ValueSet*, void)
{
    maSelectHdl.Call(*this);
    UpdatePreview();
    if (m_xControl->get_active())
        m_xControl->set_active(false);
}
 
void SvtLineListBox::UpdatePreview()
{
    SvxBorderLineStyle eStyle = GetSelectEntryStyle();
    for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i)
    {
        if (eStyle == RID_SVXSTR_BORDERLINE[i].second)
        {
            m_xControl->set_label(SvtResId(RID_SVXSTR_BORDERLINE[i].first));
            break;
        }
    }
 
    if (eStyle == SvxBorderLineStyle::NONE)
    {
        m_xControl->set_image(nullptr);
        m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE));
    }
    else
    {
        Image aImage(m_xLineSet->GetItemImage(m_xLineSet->GetSelectedItemId()));
        m_xControl->set_label(u""_ustr);
        const auto nPos = (aVirDev->GetOutputSizePixel().Height() - aImage.GetSizePixel().Height()) / 2;
        auto popIt = aVirDev->ScopedPush(vcl::PushFlags::MAPMODE);
        aVirDev->SetMapMode(MapMode(MapUnit::MapPixel));
        const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
        aVirDev->SetBackground(rSettings.GetFieldColor());
        aVirDev->Erase();
        aVirDev->DrawImage(Point(0, nPos), aImage);
        m_xControl->set_image(aVirDev.get());
    }
}
 
SvtCalendarBox::SvtCalendarBox(std::unique_ptr<weld::MenuButton> pControl, bool bUseLabel)
    : m_bUseLabel(bUseLabel)
    , m_xControl(std::move(pControl))
    , m_xBuilder(Application::CreateBuilder(m_xControl.get(), u"svt/ui/datewindow.ui"_ustr))
    , m_xTopLevel(m_xBuilder->weld_popover(u"date_popup_window"_ustr))
    , m_xCalendar(m_xBuilder->weld_calendar(u"date_picker"_ustr))
{
    m_xControl->set_popover(m_xTopLevel.get());
    m_xCalendar->connect_selected(LINK(this, SvtCalendarBox, SelectHdl));
    m_xCalendar->connect_activated(LINK(this, SvtCalendarBox, ActivateHdl));
}
 
void SvtCalendarBox::set_date(const Date& rDate)
{
    m_xCalendar->set_date(rDate);
    set_label_from_date();
}
 
void SvtCalendarBox::set_label_from_date()
{
    if (!m_bUseLabel)
        return;
    const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
    m_xControl->set_label(rLocaleData.getDate(m_xCalendar->get_date()));
}
 
IMPL_LINK_NOARG(SvtCalendarBox, SelectHdl, weld::Calendar&, void)
{
    set_label_from_date();
    m_aSelectHdl.Call(*this);
}
 
IMPL_LINK_NOARG(SvtCalendarBox, ActivateHdl, weld::Calendar&, void)
{
    if (m_xControl->get_active())
        m_xControl->set_active(false);
    m_aActivatedHdl.Call(*this);
}
 
SvtCalendarBox::~SvtCalendarBox()
{
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'CachePreview' is required to be utilized.

V1023 A pointer without owner is added to the 'm_vLineList' container by the 'emplace_back' method. A memory leak will occur in case of an exception.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.