diff options
Diffstat (limited to 'svtools/source/control/ctrlbox.cxx')
-rw-r--r-- | svtools/source/control/ctrlbox.cxx | 1672 |
1 files changed, 1672 insertions, 0 deletions
diff --git a/svtools/source/control/ctrlbox.cxx b/svtools/source/control/ctrlbox.cxx new file mode 100644 index 000000000..af86920a5 --- /dev/null +++ b/svtools/source/control/ctrlbox.cxx @@ -0,0 +1,1672 @@ +/* -*- 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 <officecfg/Office/Common.hxx> +#include <tools/stream.hxx> +#include <vcl/customweld.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/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; + +namespace +{ + 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(); + } +} + +IMPL_LINK(FontNameBox, SettingsChangedHdl, VclSimpleEvent&, rEvent, void) +{ + if (rEvent.GetId() != VclEventId::ApplicationDataChanged) + return; + + DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData()); + if (pData->GetType() == DataChangedEventType::SETTINGS) + { + gFontPreviewVirDevs.clear(); + gRenderedFontNames.clear(); + 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) + { + for (auto &rDev : gFontPreviewVirDevs) + rDev.disposeAndClear(); + gFontPreviewVirDevs.clear(); + gRenderedFontNames.clear(); + } +} + +void FontNameBox::SaveMRUEntries(const OUString& aFontMRUEntriesFile) const +{ + OString aEntries(OUStringToOString(m_xComboBox->get_mru_entries(), + RTL_TEXTENCODING_UTF8)); + + 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 ); + 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; + } + + OString aLine; + aStream.ReadLine( aLine ); + OUString aEntries = OStringToOUString(aLine, + RTL_TEXTENCODING_UTF8); + m_xComboBox->set_mru_entries(aEntries); +} + +void FontNameBox::InitFontMRUEntriesFile() +{ + OUString sUserConfigDir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"); + 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(); + OUString rEntries = m_xComboBox->get_mru_entries(); + bool bLoadFromFile = rEntries.isEmpty(); + 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 (comphelper::LibreOfficeKit::isActive()) + 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_NOARG(FontNameBox, CustomGetSizeHdl, OutputDevice&, Size) +{ + 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) +{ + 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) +{ + rDevice.Push(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); + rDevice.Pop(); +} + +OutputDevice& FontNameBox::CachePreview(size_t nIndex, Point* pTopLeft) +{ + SolarMutexGuard aGuard; + const FontMetric& rFontMetric = (*mpFontList)[nIndex]; + const OUString& rFontName = rFontMetric.GetFamilyName(); + + size_t nPreviewIndex; + auto xFind = std::find(gRenderedFontNames.begin(), gRenderedFontNames.end(), rFontName); + bool bPreviewAvailable = xFind != gRenderedFontNames.end(); + if (!bPreviewAvailable) + { + nPreviewIndex = gRenderedFontNames.size(); + gRenderedFontNames.push_back(rFontName); + } + else + nPreviewIndex = std::distance(gRenderedFontNames.begin(), xFind); + + size_t nPage = nPreviewIndex / gPreviewsPerDevice; + size_t nIndexInPage = nPreviewIndex - (nPage * gPreviewsPerDevice); + + Point aTopLeft(0, gUserItemSz.Height() * nIndexInPage); + + if (!bPreviewAvailable) + { + if (nPage >= gFontPreviewVirDevs.size()) + { + gFontPreviewVirDevs.emplace_back(m_xComboBox->create_render_virtual_device()); + VirtualDevice& rDevice = *gFontPreviewVirDevs.back(); + rDevice.SetOutputSizePixel(Size(gUserItemSz.Width(), gUserItemSz.Height() * gPreviewsPerDevice)); + weld::SetPointFont(rDevice, m_xComboBox->get_font()); + assert(gFontPreviewVirDevs.size() == nPage + 1); + } + + DrawPreview(rFontMetric, aTopLeft, *gFontPreviewVirDevs.back(), false); + } + + if (pTopLeft) + *pTopLeft = aTopLeft; + + return *gFontPreviewVirDevs[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); + } + else + { + // use cache of unselected entries + Point aTopLeft; + OutputDevice& rDevice = CachePreview(nIndex, &aTopLeft); + + rRenderContext.DrawOutDev(aDestPoint, gUserItemSz, + aTopLeft, gUserItemSz, + 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)) +{ + //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())); +} + +void FontStyleBox::Fill( std::u16string_view rName, const FontList* pList ) +{ + OUString aOldText = m_xComboBox->get_active_text(); + int nPos = m_xComboBox->get_active(); + + 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.GetWeight(); + FontItalic eItalic = aFontMetric.GetItalic(); + FontWidth eWidth = aFontMetric.GetWidthType(); + // 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(); + + if (!aOldText.isEmpty()) + { + int nFound = m_xComboBox->find_text(aOldText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + else + { + if (nPos >= m_xComboBox->get_count()) + m_xComboBox->set_active(0); + else + m_xComboBox->set_active(nPos); + } + } +} + +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_uLong nCount = aFontSizeNames.Count(); + for( sal_uLong 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; + } + + set_active_or_entry_text(aStr); + m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd); + m_xComboBox->thaw(); +} + +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) + return vcl::ConvertValue(nValue, 0, GetDecimalDigits(), GetUnit(), GetUnit()); + } + + 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 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, BitmapEx& 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->GetBitmapEx( Point(), Size( aSize.Width(), n1+nDist+n2 ) ); +} + +SvtLineListBox::SvtLineListBox(std::unique_ptr<weld::MenuButton> pControl) + : m_xControl(std::move(pControl)) + , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/linewindow.ui")) + , m_xTopLevel(m_xBuilder->weld_widget("line_popup_window")) + , m_xNoneButton(m_xBuilder->weld_button("none_line_button")) + , m_xLineSet(new ValueSet(nullptr)) + , m_xLineSetWin(new weld::CustomWeld(*m_xBuilder, "lineset", *m_xLineSet)) + , m_nWidth( 5 ) + , aVirDev(VclPtr<VirtualDevice>::Create()) + , aColor(COL_BLACK) + , maPaintCol(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_xTopLevel->connect_focus_in(LINK(this, SvtLineListBox, FocusHdl)); + m_xControl->set_popover(m_xTopLevel.get()); + m_xControl->connect_toggled(LINK(this, SvtLineListBox, ToggleHdl)); + + // 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(""); + 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)); + + UpdatePaintLineColor(); +} + +IMPL_LINK_NOARG(SvtLineListBox, FocusHdl, weld::Widget&, void) +{ + if (GetSelectEntryStyle() == SvxBorderLineStyle::NONE) + m_xNoneButton->grab_focus(); + else + m_xLineSet->GrabFocus(); +} + +IMPL_LINK(SvtLineListBox, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (rButton.get_active()) + FocusHdl(*m_xTopLevel); +} + +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; +} + +sal_Int32 SvtLineListBox::GetStylePos( sal_Int32 nListPos ) const +{ + sal_Int32 nPos = -1; + --nListPos; + + sal_Int32 n = 0; + size_t i = 0; + size_t nCount = m_vLineList.size(); + while ( nPos == -1 && i < nCount ) + { + if ( nListPos == n ) + nPos = static_cast<sal_Int32>(i); + n++; + i++; + } + + return nPos; +} + +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::UpdatePaintLineColor() +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color aNewCol(rSettings.GetWindowColor().IsDark() ? rSettings.GetLabelTextColor() : aColor); + + bool bRet = aNewCol != maPaintCol; + + if( bRet ) + maPaintCol = aNewCol; +} + +void SvtLineListBox::UpdateEntries() +{ + UpdatePaintLineColor( ); + + SvxBorderLineStyle eSelected = GetSelectEntryStyle(); + + // Remove the old entries + m_xLineSet->Clear(); + + // 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 ]; + BitmapEx aBmp; + ImpGetLine( pData->GetLine1ForWidth( m_nWidth ), + pData->GetLine2ForWidth( m_nWidth ), + pData->GetDistForWidth( m_nWidth ), + GetColorLine1(m_xLineSet->GetItemCount()), + GetColorLine2(m_xLineSet->GetItemCount()), + GetColorDist(m_xLineSet->GetItemCount()), + 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(); +} + +Color SvtLineListBox::GetColorLine1( sal_Int32 nPos ) +{ + sal_Int32 nStyle = GetStylePos( nPos ); + if (nStyle == -1) + return GetPaintColor( ); + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorLine1( GetColor( ) ); +} + +Color SvtLineListBox::GetColorLine2( sal_Int32 nPos ) +{ + sal_Int32 nStyle = GetStylePos(nPos); + if (nStyle == -1) + return GetPaintColor( ); + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorLine2( GetColor( ) ); +} + +Color SvtLineListBox::GetColorDist( sal_Int32 nPos ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color rResult = rSettings.GetFieldColor(); + + sal_Int32 nStyle = GetStylePos( nPos ); + if (nStyle == -1) + return rResult; + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorDist( GetColor( ), rResult ); +} + +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(""); + const auto nPos = (aVirDev->GetOutputSizePixel().Height() - aImage.GetSizePixel().Height()) / 2; + aVirDev->Push(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()); + aVirDev->Pop(); + } +} + +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(), "svt/ui/datewindow.ui")) + , m_xTopLevel(m_xBuilder->weld_widget("date_popup_window")) + , m_xCalendar(m_xBuilder->weld_calendar("date")) +{ + 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: */ |