xref: /core/svtools/source/control/ctrlbox.cxx (revision fcce0642)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_folders.h>
21 
22 #include <comphelper/lok.hxx>
23 #include <i18nutil/unicode.hxx>
24 #include <tools/stream.hxx>
25 #include <vcl/builder.hxx>
26 #include <vcl/customweld.hxx>
27 #include <vcl/event.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/fieldvalues.hxx>
30 #include <vcl/settings.hxx>
31 #include <vcl/image.hxx>
32 #include <vcl/virdev.hxx>
33 #include <rtl/math.hxx>
34 #include <sal/macros.h>
35 #include <sal/log.hxx>
36 #include <comphelper/processfactory.hxx>
37 #include <comphelper/string.hxx>
38 #include <unotools/charclass.hxx>
39 #include <unotools/fontoptions.hxx>
40 #include <unotools/localedatawrapper.hxx>
41 
42 #include <svtools/borderline.hxx>
43 #include <svtools/sampletext.hxx>
44 #include <svtools/svtresid.hxx>
45 #include <svtools/strings.hrc>
46 #include <svtools/ctrlbox.hxx>
47 #include <svtools/ctrltool.hxx>
48 #include <svtools/borderhelper.hxx>
49 #include <svtools/valueset.hxx>
50 
51 #include <basegfx/polygon/b2dpolygon.hxx>
52 #include <basegfx/polygon/b2dpolygontools.hxx>
53 #include <editeng/borderline.hxx>
54 
55 #include <rtl/bootstrap.hxx>
56 
57 #include <boost/property_tree/ptree.hpp>
58 
59 #include <borderline.hrc>
60 
61 #include <stdio.h>
62 
63 #define IMGOUTERTEXTSPACE 5
64 #define EXTRAFONTSIZE 5
65 #define GAPTOEXTRAPREVIEW 10
66 #define MINGAPWIDTH 2
67 
68 #define FONTNAMEBOXMRUENTRIESFILE "/user/config/fontnameboxmruentries"
69 
70 
71 BorderWidthImpl::BorderWidthImpl( BorderWidthImplFlags nFlags, double nRate1, double nRate2, double nRateGap ):
72     m_nFlags( nFlags ),
73     m_nRate1( nRate1 ),
74     m_nRate2( nRate2 ),
75     m_nRateGap( nRateGap )
76 {
77 }
78 
79 bool BorderWidthImpl::operator== ( const BorderWidthImpl& r ) const
80 {
81     return ( m_nFlags == r.m_nFlags ) &&
82            ( m_nRate1 == r.m_nRate1 ) &&
83            ( m_nRate2 == r.m_nRate2 ) &&
84            ( m_nRateGap == r.m_nRateGap );
85 }
86 
87 long BorderWidthImpl::GetLine1( long nWidth ) const
88 {
89     long result = static_cast<long>(m_nRate1);
90     if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 )
91     {
92         long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2;
93         long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap;
94         result = std::max<long>(0,
95                     static_cast<long>((m_nRate1 * nWidth) + 0.5)
96                         - (nConstant2 + nConstantD));
97         if (result == 0 && m_nRate1 > 0.0 && nWidth > 0)
98         {   // fdo#51777: hack to essentially treat 1 twip DOUBLE border
99             result = 1;  // as 1 twip SINGLE border
100         }
101     }
102     return result;
103 }
104 
105 long BorderWidthImpl::GetLine2( long nWidth ) const
106 {
107     long result = static_cast<long>(m_nRate2);
108     if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2)
109     {
110         long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1;
111         long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap;
112         result = std::max<long>(0,
113                     static_cast<long>((m_nRate2 * nWidth) + 0.5)
114                         - (nConstant1 + nConstantD));
115     }
116     return result;
117 }
118 
119 long BorderWidthImpl::GetGap( long nWidth ) const
120 {
121     long result = static_cast<long>(m_nRateGap);
122     if ( m_nFlags & BorderWidthImplFlags::CHANGE_DIST )
123     {
124         long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1;
125         long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2;
126         result = std::max<long>(0,
127                     static_cast<long>((m_nRateGap * nWidth) + 0.5)
128                         - (nConstant1 + nConstant2));
129     }
130 
131     // Avoid having too small distances (less than 0.1pt)
132     if ( result < MINGAPWIDTH && m_nRate1 > 0 && m_nRate2 > 0 )
133         result = MINGAPWIDTH;
134 
135     return result;
136 }
137 
138 static double lcl_getGuessedWidth( long nTested, double nRate, bool bChanging )
139 {
140     double nWidth = -1.0;
141     if ( bChanging )
142         nWidth = double( nTested ) / nRate;
143     else
144     {
145         if ( rtl::math::approxEqual(double( nTested ), nRate) )
146             nWidth = nRate;
147     }
148 
149     return nWidth;
150 }
151 
152 long BorderWidthImpl::GuessWidth( long nLine1, long nLine2, long nGap )
153 {
154     std::vector< double > aToCompare;
155     bool bInvalid = false;
156 
157     bool bLine1Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 );
158     double nWidth1 = lcl_getGuessedWidth( nLine1, m_nRate1, bLine1Change );
159     if ( bLine1Change )
160         aToCompare.push_back( nWidth1 );
161     else if (nWidth1 < 0)
162         bInvalid = true;
163 
164     bool bLine2Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2 );
165     double nWidth2 = lcl_getGuessedWidth( nLine2, m_nRate2, bLine2Change );
166     if ( bLine2Change )
167         aToCompare.push_back( nWidth2 );
168     else if (nWidth2 < 0)
169         bInvalid = true;
170 
171     bool bGapChange = bool( m_nFlags & BorderWidthImplFlags::CHANGE_DIST );
172     double nWidthGap = lcl_getGuessedWidth( nGap, m_nRateGap, bGapChange );
173     if ( bGapChange && nGap >= MINGAPWIDTH )
174         aToCompare.push_back( nWidthGap );
175     else if ( !bGapChange && nWidthGap < 0 )
176         bInvalid = true;
177 
178     // non-constant line width factors must sum to 1
179     assert((((bLine1Change) ? m_nRate1 : 0) +
180             ((bLine2Change) ? m_nRate2 : 0) +
181             ((bGapChange) ? m_nRateGap : 0)) - 1.0 < 0.00001 );
182 
183     double nWidth = 0.0;
184     if ( (!bInvalid) && (!aToCompare.empty()) )
185     {
186         nWidth = *aToCompare.begin();
187         for (auto const& elem : aToCompare)
188         {
189             bInvalid = ( nWidth != elem );
190             if (bInvalid)
191                 break;
192         }
193         nWidth = bInvalid ?  0.0 : nLine1 + nLine2 + nGap;
194     }
195 
196     return nWidth;
197 }
198 
199 static void lclDrawPolygon( OutputDevice& rDev, const basegfx::B2DPolygon& rPolygon, long nWidth, SvxBorderLineStyle nDashing )
200 {
201     AntialiasingFlags nOldAA = rDev.GetAntialiasing();
202     rDev.SetAntialiasing( nOldAA & ~AntialiasingFlags::EnableB2dDraw );
203 
204     long nPix = rDev.PixelToLogic(Size(1, 1)).Width();
205     basegfx::B2DPolyPolygon aPolygons = svtools::ApplyLineDashing(rPolygon, nDashing, nPix);
206 
207     // Handle problems of width 1px in Pixel mode: 0.5px gives a 1px line
208     if (rDev.GetMapMode().GetMapUnit() == MapUnit::MapPixel && nWidth == nPix)
209         nWidth = 0;
210 
211     for ( sal_uInt32 i = 0; i < aPolygons.count( ); i++ )
212     {
213         const basegfx::B2DPolygon& aDash = aPolygons.getB2DPolygon( i );
214         basegfx::B2DPoint aStart = aDash.getB2DPoint( 0 );
215         basegfx::B2DPoint aEnd = aDash.getB2DPoint( aDash.count() - 1 );
216 
217         basegfx::B2DVector aVector( aEnd - aStart );
218         aVector.normalize( );
219         const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector));
220 
221         const basegfx::B2DVector aWidthOffset( double( nWidth ) / 2 * aPerpendicular);
222         basegfx::B2DPolygon aDashPolygon;
223         aDashPolygon.append( aStart + aWidthOffset );
224         aDashPolygon.append( aEnd + aWidthOffset );
225         aDashPolygon.append( aEnd - aWidthOffset );
226         aDashPolygon.append( aStart - aWidthOffset );
227         aDashPolygon.setClosed( true );
228 
229         rDev.DrawPolygon( aDashPolygon );
230     }
231 
232     rDev.SetAntialiasing( nOldAA );
233 }
234 
235 namespace svtools {
236 
237 /**
238  * Dashing array must start with a line width and end with a blank width.
239  */
240 static std::vector<double> GetDashing( SvxBorderLineStyle nDashing )
241 {
242     std::vector<double> aPattern;
243     switch (nDashing)
244     {
245         case SvxBorderLineStyle::DOTTED:
246             aPattern.push_back( 1.0 ); // line
247             aPattern.push_back( 2.0 ); // blank
248         break;
249         case SvxBorderLineStyle::DASHED:
250             aPattern.push_back( 16.0 ); // line
251             aPattern.push_back( 5.0 );  // blank
252         break;
253         case SvxBorderLineStyle::FINE_DASHED:
254             aPattern.push_back( 6.0 ); // line
255             aPattern.push_back( 2.0 ); // blank
256         break;
257         case SvxBorderLineStyle::DASH_DOT:
258             aPattern.push_back( 16.0 ); // line
259             aPattern.push_back( 5.0 );  // blank
260             aPattern.push_back( 5.0 );  // line
261             aPattern.push_back( 5.0 );  // blank
262         break;
263         case SvxBorderLineStyle::DASH_DOT_DOT:
264             aPattern.push_back( 16.0 ); // line
265             aPattern.push_back( 5.0 );  // blank
266             aPattern.push_back( 5.0 );  // line
267             aPattern.push_back( 5.0 );  // blank
268             aPattern.push_back( 5.0 );  // line
269             aPattern.push_back( 5.0 );  // blank
270         break;
271         default:
272             ;
273     }
274 
275     return aPattern;
276 }
277 
278 namespace {
279 
280 class ApplyScale
281 {
282     double mfScale;
283 public:
284     explicit ApplyScale( double fScale ) : mfScale(fScale) {}
285     void operator() ( double& rVal )
286     {
287         rVal *= mfScale;
288     }
289 };
290 
291 }
292 
293 std::vector<double> GetLineDashing( SvxBorderLineStyle nDashing, double fScale )
294 {
295     std::vector<double> aPattern = GetDashing(nDashing);
296     std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale));
297     return aPattern;
298 }
299 
300 basegfx::B2DPolyPolygon ApplyLineDashing( const basegfx::B2DPolygon& rPolygon, SvxBorderLineStyle nDashing, double fScale )
301 {
302     std::vector<double> aPattern = GetDashing(nDashing);
303     std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale));
304 
305     basegfx::B2DPolyPolygon aPolygons;
306 
307     if (aPattern.empty())
308         aPolygons.append(rPolygon);
309     else
310         basegfx::utils::applyLineDashing(rPolygon, aPattern, &aPolygons);
311 
312     return aPolygons;
313 }
314 
315 void DrawLine( OutputDevice& rDev, const Point& rP1, const Point& rP2,
316     sal_uInt32 nWidth, SvxBorderLineStyle nDashing )
317 {
318     DrawLine( rDev, basegfx::B2DPoint( rP1.X(), rP1.Y() ),
319             basegfx::B2DPoint( rP2.X(), rP2.Y( ) ), nWidth, nDashing );
320 }
321 
322 void DrawLine( OutputDevice& rDev, const basegfx::B2DPoint& rP1, const basegfx::B2DPoint& rP2,
323     sal_uInt32 nWidth, SvxBorderLineStyle nDashing )
324 {
325     basegfx::B2DPolygon aPolygon;
326     aPolygon.append( rP1 );
327     aPolygon.append( rP2 );
328     lclDrawPolygon( rDev, aPolygon, nWidth, nDashing );
329 }
330 
331 }
332 
333 static Size gUserItemSz;
334 static int gFontNameBoxes;
335 static size_t gPreviewsPerDevice;
336 static std::vector<VclPtr<VirtualDevice>> gFontPreviewVirDevs;
337 static std::vector<OUString> gRenderedFontNames;
338 
339 FontNameBox::FontNameBox(std::unique_ptr<weld::ComboBox> p)
340     : m_xComboBox(std::move(p))
341     , mnPreviewProgress(0)
342     , mbWYSIWYG(false)
343     , maUpdateIdle("FontNameBox Preview Update")
344 {
345     ++gFontNameBoxes;
346     InitFontMRUEntriesFile();
347 
348     maUpdateIdle.SetPriority(TaskPriority::LOWEST);
349     maUpdateIdle.SetInvokeHandler(LINK(this, FontNameBox, UpdateHdl));
350 }
351 
352 FontNameBox::~FontNameBox()
353 {
354     if (mpFontList)
355     {
356         SaveMRUEntries (maFontMRUEntriesFile);
357         ImplDestroyFontList();
358     }
359     --gFontNameBoxes;
360     if (!gFontNameBoxes)
361     {
362         gFontPreviewVirDevs.clear();
363         gRenderedFontNames.clear();
364     }
365 }
366 
367 void FontNameBox::SaveMRUEntries(const OUString& aFontMRUEntriesFile) const
368 {
369     OString aEntries(OUStringToOString(m_xComboBox->get_mru_entries(),
370         RTL_TEXTENCODING_UTF8));
371 
372     if (aEntries.isEmpty() || aFontMRUEntriesFile.isEmpty())
373         return;
374 
375     SvFileStream aStream;
376     aStream.Open( aFontMRUEntriesFile, StreamMode::WRITE | StreamMode::TRUNC );
377     if( ! (aStream.IsOpen() && aStream.IsWritable()) )
378     {
379         SAL_INFO("svtools.control", "FontNameBox::SaveMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed");
380         return;
381     }
382 
383     aStream.SetLineDelimiter( LINEEND_LF );
384     aStream.WriteLine( aEntries );
385     aStream.WriteLine( OString() );
386 }
387 
388 void FontNameBox::LoadMRUEntries( const OUString& aFontMRUEntriesFile )
389 {
390     if (aFontMRUEntriesFile.isEmpty())
391         return;
392 
393     SvtFontOptions aFontOpt;
394     if (!aFontOpt.IsFontHistoryEnabled())
395         return;
396 
397     SvFileStream aStream( aFontMRUEntriesFile, StreamMode::READ );
398     if( ! aStream.IsOpen() )
399     {
400         SAL_INFO("svtools.control", "FontNameBox::LoadMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed");
401         return;
402     }
403 
404     OString aLine;
405     aStream.ReadLine( aLine );
406     OUString aEntries = OStringToOUString(aLine,
407         RTL_TEXTENCODING_UTF8);
408     m_xComboBox->set_mru_entries(aEntries);
409 }
410 
411 void FontNameBox::InitFontMRUEntriesFile()
412 {
413     OUString sUserConfigDir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}");
414     rtl::Bootstrap::expandMacros(sUserConfigDir);
415 
416     maFontMRUEntriesFile = sUserConfigDir;
417     if( !maFontMRUEntriesFile.isEmpty() )
418     {
419         maFontMRUEntriesFile += FONTNAMEBOXMRUENTRIESFILE;
420     }
421 }
422 
423 void FontNameBox::ImplDestroyFontList()
424 {
425     mpFontList.reset();
426     mnPreviewProgress = 0;
427     maUpdateIdle.Stop();
428 }
429 
430 void FontNameBox::Fill( const FontList* pList )
431 {
432     // store old text and clear box
433     OUString aOldText = m_xComboBox->get_active_text();
434     OUString rEntries = m_xComboBox->get_mru_entries();
435     bool bLoadFromFile = rEntries.isEmpty();
436     m_xComboBox->freeze();
437     m_xComboBox->clear();
438 
439     ImplDestroyFontList();
440     mpFontList.reset(new ImplFontList);
441 
442     // insert fonts
443     size_t nFontCount = pList->GetFontNameCount();
444     for (size_t i = 0; i < nFontCount; ++i)
445     {
446         const FontMetric& rFontMetric = pList->GetFontName(i);
447         m_xComboBox->append(OUString::number(i), rFontMetric.GetFamilyName());
448         mpFontList->push_back(rFontMetric);
449     }
450 
451     if (bLoadFromFile)
452         LoadMRUEntries(maFontMRUEntriesFile);
453     else
454         m_xComboBox->set_mru_entries(rEntries);
455 
456     m_xComboBox->thaw();
457 
458     if (mbWYSIWYG && nFontCount)
459     {
460         assert(mnPreviewProgress == 0 && "ImplDestroyFontList wasn't called");
461         maUpdateIdle.Start();
462     }
463 
464     // restore text
465     if (!aOldText.isEmpty())
466         set_active_or_entry_text(aOldText);
467 }
468 
469 void FontNameBox::EnableWYSIWYG()
470 {
471     if (mbWYSIWYG || comphelper::LibreOfficeKit::isActive())
472         return;
473     mbWYSIWYG = true;
474 
475     static bool bGlobalsInited;
476     if (!bGlobalsInited)
477     {
478         gUserItemSz = Size(m_xComboBox->get_approximate_digit_width() * 52, m_xComboBox->get_text_height());
479         gUserItemSz.setHeight(gUserItemSz.Height() * 16);
480         gUserItemSz.setHeight(gUserItemSz.Height() / 10);
481 
482         size_t nMaxDeviceHeight = SAL_MAX_INT16 / 2; // see limitXCreatePixmap
483         gPreviewsPerDevice = nMaxDeviceHeight / gUserItemSz.Height();
484 
485         bGlobalsInited = true;
486     }
487 
488     m_xComboBox->connect_custom_get_size(LINK(this, FontNameBox, CustomGetSizeHdl));
489     m_xComboBox->connect_custom_render(LINK(this, FontNameBox, CustomRenderHdl));
490     m_xComboBox->set_custom_renderer();
491 
492     mbWYSIWYG = true;
493 }
494 
495 IMPL_STATIC_LINK_NOARG(FontNameBox, CustomGetSizeHdl, OutputDevice&, Size)
496 {
497     return gUserItemSz;
498 }
499 
500 namespace
501 {
502     long shrinkFontToFit(OUString const &rSampleText, long nH, vcl::Font &rFont, OutputDevice &rDevice, tools::Rectangle &rTextRect)
503     {
504         long nWidth = 0;
505 
506         Size aSize( rFont.GetFontSize() );
507 
508         //Make sure it fits in the available height
509         while (aSize.Height() > 0)
510         {
511             if (!rDevice.GetTextBoundRect(rTextRect, rSampleText))
512                 break;
513             if (rTextRect.GetHeight() <= nH)
514             {
515                 nWidth = rTextRect.GetWidth();
516                 break;
517             }
518 
519             aSize.AdjustHeight( -(EXTRAFONTSIZE) );
520             rFont.SetFontSize(aSize);
521             rDevice.SetFont(rFont);
522         }
523 
524         return nWidth;
525     }
526 }
527 
528 IMPL_LINK_NOARG(FontNameBox, UpdateHdl, Timer*, void)
529 {
530     CachePreview(mnPreviewProgress++, nullptr);
531     // tdf#132536 limit to ~25 pre-rendered for now. The font caches look
532     // b0rked, the massive charmaps are ~never swapped out, and don't count
533     // towards the size of a font in the font cache and if the freetype font
534     // cache size is set experimentally very low then we crash, so there's an
535     // awful lot to consider there.
536     if (mnPreviewProgress < std::min<size_t>(25, mpFontList->size()))
537         maUpdateIdle.Start();
538 }
539 
540 static void DrawPreview(const FontMetric& rFontMetric, const Point& rTopLeft, OutputDevice& rDevice, bool bSelected)
541 {
542     rDevice.Push(PushFlags::TEXTCOLOR);
543 
544     const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
545     if (bSelected)
546         rDevice.SetTextColor(rStyleSettings.GetHighlightTextColor());
547     else
548         rDevice.SetTextColor(rStyleSettings.GetDialogTextColor());
549 
550     long nX = rTopLeft.X();
551     long nH = gUserItemSz.Height();
552 
553     nX += IMGOUTERTEXTSPACE;
554 
555     const bool bSymbolFont = isSymbolFont(rFontMetric);
556 
557     vcl::Font aOldFont(rDevice.GetFont());
558     Size aSize( aOldFont.GetFontSize() );
559     aSize.AdjustHeight(EXTRAFONTSIZE );
560     vcl::Font aFont( rFontMetric );
561     aFont.SetFontSize( aSize );
562     rDevice.SetFont(aFont);
563 
564     bool bUsingCorrectFont = true;
565     tools::Rectangle aTextRect;
566 
567     // Preview the font name
568     const OUString& sFontName = rFontMetric.GetFamilyName();
569 
570     //If it shouldn't or can't draw its own name because it doesn't have the glyphs
571     if (!canRenderNameOfSelectedFont(rDevice))
572         bUsingCorrectFont = false;
573     else
574     {
575         //Make sure it fits in the available height, shrinking the font if necessary
576         bUsingCorrectFont = shrinkFontToFit(sFontName, nH, aFont, rDevice, aTextRect) != 0;
577     }
578 
579     if (!bUsingCorrectFont)
580     {
581         rDevice.SetFont(aOldFont);
582         rDevice.GetTextBoundRect(aTextRect, sFontName);
583     }
584 
585     long nTextHeight = aTextRect.GetHeight();
586     long nDesiredGap = (nH-nTextHeight)/2;
587     long nVertAdjust = nDesiredGap - aTextRect.Top();
588     Point aPos( nX, rTopLeft.Y() + nVertAdjust );
589     rDevice.DrawText(aPos, sFontName);
590     long nTextX = aPos.X() + aTextRect.GetWidth() + GAPTOEXTRAPREVIEW;
591 
592     if (!bUsingCorrectFont)
593         rDevice.SetFont(aFont);
594 
595     OUString sSampleText;
596 
597     if (!bSymbolFont)
598     {
599         const bool bNameBeginsWithLatinText = rFontMetric.GetFamilyName()[0] <= 'z';
600 
601         if (bNameBeginsWithLatinText || !bUsingCorrectFont)
602             sSampleText = makeShortRepresentativeTextForSelectedFont(rDevice);
603     }
604 
605     //If we're not a symbol font, but could neither render our own name and
606     //we can't determine what script it would like to render, then try a
607     //few well known scripts
608     if (sSampleText.isEmpty() && !bUsingCorrectFont)
609     {
610         static const UScriptCode aScripts[] =
611         {
612             USCRIPT_ARABIC,
613             USCRIPT_HEBREW,
614 
615             USCRIPT_BENGALI,
616             USCRIPT_GURMUKHI,
617             USCRIPT_GUJARATI,
618             USCRIPT_ORIYA,
619             USCRIPT_TAMIL,
620             USCRIPT_TELUGU,
621             USCRIPT_KANNADA,
622             USCRIPT_MALAYALAM,
623             USCRIPT_SINHALA,
624             USCRIPT_DEVANAGARI,
625 
626             USCRIPT_THAI,
627             USCRIPT_LAO,
628             USCRIPT_GEORGIAN,
629             USCRIPT_TIBETAN,
630             USCRIPT_SYRIAC,
631             USCRIPT_MYANMAR,
632             USCRIPT_ETHIOPIC,
633             USCRIPT_KHMER,
634             USCRIPT_MONGOLIAN,
635 
636             USCRIPT_KOREAN,
637             USCRIPT_JAPANESE,
638             USCRIPT_HAN,
639             USCRIPT_SIMPLIFIED_HAN,
640             USCRIPT_TRADITIONAL_HAN,
641 
642             USCRIPT_GREEK
643         };
644 
645         for (const UScriptCode& rScript : aScripts)
646         {
647             OUString sText = makeShortRepresentativeTextForScript(rScript);
648             if (!sText.isEmpty())
649             {
650                 bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText));
651                 if (bHasSampleTextGlyphs)
652                 {
653                     sSampleText = sText;
654                     break;
655                 }
656             }
657         }
658 
659         static const UScriptCode aMinimalScripts[] =
660         {
661             USCRIPT_HEBREW, //e.g. biblical hebrew
662             USCRIPT_GREEK
663         };
664 
665         for (const UScriptCode& rMinimalScript : aMinimalScripts)
666         {
667             OUString sText = makeShortMinimalTextForScript(rMinimalScript);
668             if (!sText.isEmpty())
669             {
670                 bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText));
671                 if (bHasSampleTextGlyphs)
672                 {
673                     sSampleText = sText;
674                     break;
675                 }
676             }
677         }
678     }
679 
680     //If we're a symbol font, or for some reason the font still couldn't
681     //render something representative of what it would like to render then
682     //make up some semi-random text that it *can* display
683     if (bSymbolFont || (!bUsingCorrectFont && sSampleText.isEmpty()))
684         sSampleText = makeShortRepresentativeSymbolTextForSelectedFont(rDevice);
685 
686     if (!sSampleText.isEmpty())
687     {
688         const Size &rItemSize = gUserItemSz;
689 
690         //leave a little border at the edge
691         long nSpace = rItemSize.Width() - nTextX - IMGOUTERTEXTSPACE;
692         if (nSpace >= 0)
693         {
694             //Make sure it fits in the available height, and get how wide that would be
695             long nWidth = shrinkFontToFit(sSampleText, nH, aFont, rDevice, aTextRect);
696             //Chop letters off until it fits in the available width
697             while (nWidth > nSpace || nWidth > gUserItemSz.Width())
698             {
699                 sSampleText = sSampleText.copy(0, sSampleText.getLength()-1);
700                 nWidth = rDevice.GetTextBoundRect(aTextRect, sSampleText) ?
701                          aTextRect.GetWidth() : 0;
702             }
703 
704             //center the text on the line
705             if (!sSampleText.isEmpty() && nWidth)
706             {
707                 nTextHeight = aTextRect.GetHeight();
708                 nDesiredGap = (nH-nTextHeight)/2;
709                 nVertAdjust = nDesiredGap - aTextRect.Top();
710                 aPos = Point(nTextX + nSpace - nWidth, rTopLeft.Y() + nVertAdjust);
711                 rDevice.DrawText(aPos, sSampleText);
712             }
713         }
714     }
715 
716     rDevice.SetFont(aOldFont);
717     rDevice.Pop();
718 }
719 
720 OutputDevice& FontNameBox::CachePreview(size_t nIndex, Point* pTopLeft)
721 {
722     SolarMutexGuard aGuard;
723     const FontMetric& rFontMetric = (*mpFontList)[nIndex];
724     const OUString& rFontName = rFontMetric.GetFamilyName();
725 
726     size_t nPreviewIndex;
727     auto xFind = std::find(gRenderedFontNames.begin(), gRenderedFontNames.end(), rFontName);
728     bool bPreviewAvailable = xFind != gRenderedFontNames.end();
729     if (!bPreviewAvailable)
730     {
731         nPreviewIndex = gRenderedFontNames.size();
732         gRenderedFontNames.push_back(rFontName);
733     }
734     else
735         nPreviewIndex = std::distance(gRenderedFontNames.begin(), xFind);
736 
737     size_t nPage = nPreviewIndex / gPreviewsPerDevice;
738     size_t nIndexInPage = nPreviewIndex - (nPage * gPreviewsPerDevice);
739 
740     Point aTopLeft(0, gUserItemSz.Height() * nIndexInPage);
741 
742     if (!bPreviewAvailable)
743     {
744         if (nPage >= gFontPreviewVirDevs.size())
745         {
746             gFontPreviewVirDevs.emplace_back(m_xComboBox->create_render_virtual_device());
747             VirtualDevice& rDevice = *gFontPreviewVirDevs.back();
748             rDevice.SetOutputSizePixel(Size(gUserItemSz.Width(), gUserItemSz.Height() * gPreviewsPerDevice));
749             if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
750                 pDefaultDevice->SetPointFont(rDevice, m_xComboBox->get_font());
751             assert(gFontPreviewVirDevs.size() == nPage + 1);
752         }
753 
754         DrawPreview(rFontMetric, aTopLeft, *gFontPreviewVirDevs.back(), false);
755     }
756 
757     if (pTopLeft)
758         *pTopLeft = aTopLeft;
759 
760     return *gFontPreviewVirDevs[nPage];
761 }
762 
763 IMPL_LINK(FontNameBox, CustomRenderHdl, weld::ComboBox::render_args, aPayload, void)
764 {
765     vcl::RenderContext& rRenderContext = std::get<0>(aPayload);
766     const ::tools::Rectangle& rRect = std::get<1>(aPayload);
767     bool bSelected = std::get<2>(aPayload);
768     const OUString& rId = std::get<3>(aPayload);
769 
770     sal_uInt32 nIndex = rId.toUInt32();
771 
772     Point aDestPoint(rRect.TopLeft());
773     auto nMargin = (rRect.GetHeight() - gUserItemSz.Height()) / 2;
774     aDestPoint.AdjustY(nMargin);
775 
776     if (bSelected)
777     {
778         const FontMetric& rFontMetric = (*mpFontList)[nIndex];
779         DrawPreview(rFontMetric, aDestPoint, rRenderContext, true);
780     }
781     else
782     {
783         // use cache of unselected entries
784         Point aTopLeft;
785         OutputDevice& rDevice = CachePreview(nIndex, &aTopLeft);
786 
787         rRenderContext.DrawOutDev(aDestPoint, gUserItemSz,
788                                   aTopLeft, gUserItemSz,
789                                   rDevice);
790     }
791 }
792 
793 void FontNameBox::set_active_or_entry_text(const OUString& rText)
794 {
795     const int nFound = m_xComboBox->find_text(rText);
796     if (nFound != -1)
797         m_xComboBox->set_active(nFound);
798     m_xComboBox->set_entry_text(rText);
799 }
800 
801 FontStyleBox::FontStyleBox(std::unique_ptr<weld::ComboBox> p)
802     : m_xComboBox(std::move(p))
803 {
804     //Use the standard texts to get an optimal size and stick to that size.
805     //That should stop the character dialog dancing around.
806     auto nMaxLen = m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT)).Width();
807     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT_ITALIC)).Width());
808     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL)).Width());
809     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL_ITALIC)).Width());
810     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD)).Width());
811     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD_ITALIC)).Width());
812     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK)).Width());
813     nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK_ITALIC)).Width());
814     m_xComboBox->set_entry_width_chars(std::ceil(nMaxLen / m_xComboBox->get_approximate_digit_width()));
815 }
816 
817 void FontStyleBox::Fill( const OUString& rName, const FontList* pList )
818 {
819     m_xComboBox->freeze();
820     OUString aOldText = m_xComboBox->get_active_text();
821     int nPos = m_xComboBox->get_active();
822     m_xComboBox->clear();
823 
824     // does a font with this name already exist?
825     sal_Handle hFontMetric = pList->GetFirstFontMetric( rName );
826     if ( hFontMetric )
827     {
828         OUString aStyleText;
829         FontWeight  eLastWeight = WEIGHT_DONTKNOW;
830         FontItalic  eLastItalic = ITALIC_NONE;
831         FontWidth   eLastWidth = WIDTH_DONTKNOW;
832         bool        bNormal = false;
833         bool        bItalic = false;
834         bool        bBold = false;
835         bool        bBoldItalic = false;
836         bool        bInsert = false;
837         FontMetric    aFontMetric;
838         while ( hFontMetric )
839         {
840             aFontMetric = FontList::GetFontMetric( hFontMetric );
841 
842             FontWeight  eWeight = aFontMetric.GetWeight();
843             FontItalic  eItalic = aFontMetric.GetItalic();
844             FontWidth   eWidth = aFontMetric.GetWidthType();
845             // Only if the attributes are different, we insert the
846             // Font to avoid double Entries in different languages
847             if ( (eWeight != eLastWeight) || (eItalic != eLastItalic) ||
848                  (eWidth != eLastWidth) )
849             {
850                 if ( bInsert )
851                     m_xComboBox->append_text(aStyleText);
852 
853                 if ( eWeight <= WEIGHT_NORMAL )
854                 {
855                     if ( eItalic != ITALIC_NONE )
856                         bItalic = true;
857                     else
858                         bNormal = true;
859                 }
860                 else
861                 {
862                     if ( eItalic != ITALIC_NONE )
863                         bBoldItalic = true;
864                     else
865                         bBold = true;
866                 }
867 
868                 // For wrong StyleNames we replace this with the correct once
869                 aStyleText = pList->GetStyleName( aFontMetric );
870                 bInsert = m_xComboBox->find_text(aStyleText) == -1;
871                 if ( !bInsert )
872                 {
873                     aStyleText = pList->GetStyleName( eWeight, eItalic );
874                     bInsert = m_xComboBox->find_text(aStyleText) == -1;
875                 }
876 
877                 eLastWeight = eWeight;
878                 eLastItalic = eItalic;
879                 eLastWidth = eWidth;
880             }
881             else
882             {
883                 if ( bInsert )
884                 {
885                     // If we have two names for the same attributes
886                     // we prefer the translated standard names
887                     const OUString& rAttrStyleText = pList->GetStyleName( eWeight, eItalic );
888                     if (rAttrStyleText != aStyleText)
889                     {
890                         OUString aTempStyleText = pList->GetStyleName( aFontMetric );
891                         if (rAttrStyleText == aTempStyleText)
892                             aStyleText = rAttrStyleText;
893                         bInsert = m_xComboBox->find_text(aStyleText) == -1;
894                     }
895                 }
896             }
897 
898             if ( !bItalic && (aStyleText == pList->GetItalicStr()) )
899                 bItalic = true;
900             else if ( !bBold && (aStyleText == pList->GetBoldStr()) )
901                 bBold = true;
902             else if ( !bBoldItalic && (aStyleText == pList->GetBoldItalicStr()) )
903                 bBoldItalic = true;
904 
905             hFontMetric = FontList::GetNextFontMetric( hFontMetric );
906         }
907 
908         if ( bInsert )
909             m_xComboBox->append_text(aStyleText);
910 
911         // certain style as copy
912         if ( bNormal )
913         {
914             if ( !bItalic )
915                 m_xComboBox->append_text(pList->GetItalicStr());
916             if ( !bBold )
917                 m_xComboBox->append_text(pList->GetBoldStr());
918         }
919         if ( !bBoldItalic )
920         {
921             if ( bNormal || bItalic || bBold )
922                 m_xComboBox->append_text(pList->GetBoldItalicStr());
923         }
924         if (!aOldText.isEmpty())
925         {
926             int nFound = m_xComboBox->find_text(aOldText);
927             if (nFound != -1)
928                 m_xComboBox->set_active(nFound);
929             else
930             {
931                 if (nPos >= m_xComboBox->get_count())
932                     m_xComboBox->set_active(0);
933                 else
934                     m_xComboBox->set_active(nPos);
935             }
936         }
937     }
938     else
939     {
940         // insert standard styles if no font
941         m_xComboBox->append_text(pList->GetNormalStr());
942         m_xComboBox->append_text(pList->GetItalicStr());
943         m_xComboBox->append_text(pList->GetBoldStr());
944         m_xComboBox->append_text(pList->GetBoldItalicStr());
945         if (!aOldText.isEmpty())
946         {
947             if (nPos >= m_xComboBox->get_count())
948                 m_xComboBox->set_active(0);
949             else
950                 m_xComboBox->set_active(nPos);
951         }
952     }
953     m_xComboBox->thaw();
954 }
955 
956 FontSizeBox::FontSizeBox(std::unique_ptr<weld::ComboBox> p)
957     : pFontList(nullptr)
958     , nSavedValue(0)
959     , nMin(20)
960     , nMax(9999)
961     , eUnit(FieldUnit::POINT)
962     , nDecimalDigits(1)
963     , nRelMin(0)
964     , nRelMax(0)
965     , nRelStep(0)
966     , nPtRelMin(0)
967     , nPtRelMax(0)
968     , nPtRelStep(0)
969     , bRelativeMode(false)
970     , bRelative(false)
971     , bPtRelative(false)
972     , bStdSize(false)
973     , m_xComboBox(std::move(p))
974 {
975     m_xComboBox->set_entry_width_chars(std::ceil(m_xComboBox->get_pixel_size(format_number(105)).Width() /
976                                                  m_xComboBox->get_approximate_digit_width()));
977     m_xComboBox->connect_focus_out(LINK(this, FontSizeBox, ReformatHdl));
978     m_xComboBox->connect_changed(LINK(this, FontSizeBox, ModifyHdl));
979 }
980 
981 void FontSizeBox::set_active_or_entry_text(const OUString& rText)
982 {
983     const int nFound = m_xComboBox->find_text(rText);
984     if (nFound != -1)
985         m_xComboBox->set_active(nFound);
986     m_xComboBox->set_entry_text(rText);
987 }
988 
989 IMPL_LINK(FontSizeBox, ReformatHdl, weld::Widget&, rWidget, void)
990 {
991     FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType());
992     if (!bRelativeMode || !aFontSizeNames.IsEmpty())
993     {
994         if (aFontSizeNames.Name2Size(m_xComboBox->get_active_text()) != 0)
995             return;
996     }
997 
998     set_value(get_value());
999 
1000     m_aFocusOutHdl.Call(rWidget);
1001 }
1002 
1003 IMPL_LINK(FontSizeBox, ModifyHdl, weld::ComboBox&, rBox, void)
1004 {
1005     if (bRelativeMode)
1006     {
1007         OUString aStr = comphelper::string::stripStart(rBox.get_active_text(), ' ');
1008 
1009         bool bNewMode = bRelative;
1010         bool bOldPtRelMode = bPtRelative;
1011 
1012         if ( bRelative )
1013         {
1014             bPtRelative = false;
1015             const sal_Unicode* pStr = aStr.getStr();
1016             while ( *pStr )
1017             {
1018                 if ( ((*pStr < '0') || (*pStr > '9')) && (*pStr != '%') && !unicode::isSpace(*pStr) )
1019                 {
1020                     if ( ('-' == *pStr || '+' == *pStr) && !bPtRelative )
1021                         bPtRelative = true;
1022                     else if ( bPtRelative && 'p' == *pStr && 't' == *++pStr )
1023                         ;
1024                     else
1025                     {
1026                         bNewMode = false;
1027                         break;
1028                     }
1029                 }
1030                 pStr++;
1031             }
1032         }
1033         else if (!aStr.isEmpty())
1034         {
1035             if ( -1 != aStr.indexOf('%') )
1036             {
1037                 bNewMode = true;
1038                 bPtRelative = false;
1039             }
1040 
1041             if ( '-' == aStr[0] || '+' == aStr[0] )
1042             {
1043                 bNewMode = true;
1044                 bPtRelative = true;
1045             }
1046         }
1047 
1048         if ( bNewMode != bRelative || bPtRelative != bOldPtRelMode )
1049             SetRelative( bNewMode );
1050     }
1051     m_aChangeHdl.Call(rBox);
1052 }
1053 
1054 void FontSizeBox::Fill( const FontMetric* pFontMetric, const FontList* pList )
1055 {
1056     // remember for relative mode
1057     pFontList = pList;
1058 
1059     // no font sizes need to be set for relative mode
1060     if ( bRelative )
1061         return;
1062 
1063     // query font sizes
1064     const sal_IntPtr* pTempAry;
1065     const sal_IntPtr* pAry = nullptr;
1066 
1067     if( pFontMetric )
1068     {
1069         aFontMetric = *pFontMetric;
1070         pAry = pList->GetSizeAry( *pFontMetric );
1071     }
1072     else
1073     {
1074         pAry = FontList::GetStdSizeAry();
1075     }
1076 
1077     // first insert font size names (for simplified/traditional chinese)
1078     FontSizeNames aFontSizeNames( Application::GetSettings().GetUILanguageTag().getLanguageType() );
1079     if ( pAry == FontList::GetStdSizeAry() )
1080     {
1081         // for standard sizes we don't need to bother
1082         if (bStdSize && m_xComboBox->get_count() && aFontSizeNames.IsEmpty())
1083             return;
1084         bStdSize = true;
1085     }
1086     else
1087         bStdSize = false;
1088 
1089     int nSelectionStart, nSelectionEnd;
1090     m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd);
1091     OUString aStr = m_xComboBox->get_active_text();
1092 
1093     m_xComboBox->freeze();
1094     m_xComboBox->clear();
1095     int nPos = 0;
1096 
1097     if ( !aFontSizeNames.IsEmpty() )
1098     {
1099         if ( pAry == FontList::GetStdSizeAry() )
1100         {
1101             // for scalable fonts all font size names
1102             sal_uLong nCount = aFontSizeNames.Count();
1103             for( sal_uLong i = 0; i < nCount; i++ )
1104             {
1105                 OUString    aSizeName = aFontSizeNames.GetIndexName( i );
1106                 sal_IntPtr  nSize = aFontSizeNames.GetIndexSize( i );
1107                 OUString sId(OUString::number(-nSize)); // mark as special
1108                 m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr);
1109                 nPos++;
1110             }
1111         }
1112         else
1113         {
1114             // for fixed size fonts only selectable font size names
1115             pTempAry = pAry;
1116             while ( *pTempAry )
1117             {
1118                 OUString aSizeName = aFontSizeNames.Size2Name( *pTempAry );
1119                 if ( !aSizeName.isEmpty() )
1120                 {
1121                     OUString sId(OUString::number(-(*pTempAry))); // mark as special
1122                     m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr);
1123                     nPos++;
1124                 }
1125                 pTempAry++;
1126             }
1127         }
1128     }
1129 
1130     // then insert numerical font size values
1131     pTempAry = pAry;
1132     while (*pTempAry)
1133     {
1134         InsertValue(*pTempAry);
1135         ++pTempAry;
1136     }
1137 
1138     set_active_or_entry_text(aStr);
1139     m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd);
1140     m_xComboBox->thaw();
1141 }
1142 
1143 void FontSizeBox::EnableRelativeMode( sal_uInt16 nNewMin, sal_uInt16 nNewMax, sal_uInt16 nStep )
1144 {
1145     bRelativeMode = true;
1146     nRelMin       = nNewMin;
1147     nRelMax       = nNewMax;
1148     nRelStep      = nStep;
1149     SetUnit(FieldUnit::POINT);
1150 }
1151 
1152 void FontSizeBox::EnablePtRelativeMode( short nNewMin, short nNewMax, short nStep )
1153 {
1154     bRelativeMode = true;
1155     nPtRelMin     = nNewMin;
1156     nPtRelMax     = nNewMax;
1157     nPtRelStep    = nStep;
1158     SetUnit(FieldUnit::POINT);
1159 }
1160 
1161 void FontSizeBox::InsertValue(int i)
1162 {
1163     OUString sNumber(OUString::number(i));
1164     m_xComboBox->append(sNumber, format_number(i));
1165 }
1166 
1167 void FontSizeBox::SetRelative( bool bNewRelative )
1168 {
1169     if ( !bRelativeMode )
1170         return;
1171 
1172     int nSelectionStart, nSelectionEnd;
1173     m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd);
1174     OUString aStr = comphelper::string::stripStart(m_xComboBox->get_active_text(), ' ');
1175 
1176     if (bNewRelative)
1177     {
1178         bRelative = true;
1179         bStdSize = false;
1180 
1181         m_xComboBox->clear();
1182 
1183         if (bPtRelative)
1184         {
1185             SetDecimalDigits( 1 );
1186             SetRange(nPtRelMin, nPtRelMax);
1187             SetUnit(FieldUnit::POINT);
1188 
1189             short i = nPtRelMin, n = 0;
1190             // JP 30.06.98: more than 100 values are not useful
1191             while ( i <= nPtRelMax && n++ < 100 )
1192             {
1193                 InsertValue( i );
1194                 i = i + nPtRelStep;
1195             }
1196         }
1197         else
1198         {
1199             SetDecimalDigits(0);
1200             SetRange(nRelMin, nRelMax);
1201             SetUnit(FieldUnit::PERCENT);
1202 
1203             sal_uInt16 i = nRelMin;
1204             while ( i <= nRelMax )
1205             {
1206                 InsertValue( i );
1207                 i = i + nRelStep;
1208             }
1209         }
1210     }
1211     else
1212     {
1213         if (pFontList)
1214             m_xComboBox->clear();
1215         bRelative = bPtRelative = false;
1216         SetDecimalDigits(1);
1217         SetRange(20, 9999);
1218         SetUnit(FieldUnit::POINT);
1219         if ( pFontList)
1220             Fill( &aFontMetric, pFontList );
1221     }
1222 
1223     set_active_or_entry_text(aStr);
1224     m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd);
1225 }
1226 
1227 OUString FontSizeBox::format_number(int nValue) const
1228 {
1229     OUString sRet;
1230 
1231     //pawn percent off to icu to decide whether percent is separated from its number for this locale
1232     if (eUnit == FieldUnit::PERCENT)
1233     {
1234         double fValue = nValue;
1235         fValue /= weld::SpinButton::Power10(nDecimalDigits);
1236         sRet = unicode::formatPercent(fValue, Application::GetSettings().GetUILanguageTag());
1237     }
1238     else
1239     {
1240         const SvtSysLocale aSysLocale;
1241         const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
1242         sRet = rLocaleData.getNum(nValue, nDecimalDigits, true, false);
1243         if (eUnit != FieldUnit::NONE && eUnit != FieldUnit::DEGREE)
1244             sRet += " ";
1245         assert(eUnit != FieldUnit::PERCENT);
1246         sRet += weld::MetricSpinButton::MetricToString(eUnit);
1247     }
1248 
1249     if (bRelativeMode && bPtRelative && (0 <= nValue) && !sRet.isEmpty())
1250         sRet = "+" + sRet;
1251 
1252     return sRet;
1253 }
1254 
1255 void FontSizeBox::SetValue(int nNewValue, FieldUnit eInUnit)
1256 {
1257     auto nTempValue = vcl::ConvertValue(nNewValue, 0, GetDecimalDigits(), eInUnit, GetUnit());
1258     if (nTempValue < nMin)
1259         nTempValue = nMin;
1260     else if (nTempValue > nMax)
1261         nTempValue = nMax;
1262     if (!bRelative)
1263     {
1264         FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType());
1265         // conversion loses precision; however font sizes should
1266         // never have a problem with that
1267         OUString aName = aFontSizeNames.Size2Name(nTempValue);
1268         if (!aName.isEmpty() && m_xComboBox->find_text(aName) != -1)
1269         {
1270             m_xComboBox->set_active_text(aName);
1271             return;
1272         }
1273     }
1274     OUString aResult = format_number(nTempValue);
1275     set_active_or_entry_text(aResult);
1276 }
1277 
1278 void FontSizeBox::set_value(int nNewValue)
1279 {
1280     SetValue(nNewValue, eUnit);
1281 }
1282 
1283 int FontSizeBox::get_value() const
1284 {
1285     OUString aStr = m_xComboBox->get_active_text();
1286     if (!bRelative)
1287     {
1288         FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType());
1289         auto nValue = aFontSizeNames.Name2Size(aStr);
1290         if (nValue)
1291             return vcl::ConvertValue(nValue, 0, GetDecimalDigits(), GetUnit(), GetUnit());
1292     }
1293 
1294     const SvtSysLocale aSysLocale;
1295     const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData();
1296     double fResult(0.0);
1297     (void)vcl::TextToValue(aStr, fResult, 0, GetDecimalDigits(), rLocaleData, GetUnit());
1298     if (!aStr.isEmpty())
1299     {
1300         if (fResult < nMin)
1301             fResult = nMin;
1302         else if (fResult > nMax)
1303             fResult = nMax;
1304     }
1305     return fResult;
1306 }
1307 
1308 SvxBorderLineStyle SvtLineListBox::GetSelectEntryStyle() const
1309 {
1310     if (m_xLineSet->IsNoSelection())
1311         return SvxBorderLineStyle::NONE;
1312     auto nId = m_xLineSet->GetSelectedItemId();
1313     return static_cast<SvxBorderLineStyle>(nId - 1);
1314 }
1315 
1316 namespace
1317 {
1318     Size getPreviewSize(const weld::Widget& rControl)
1319     {
1320         return Size(rControl.get_approximate_digit_width() * 15, rControl.get_text_height());
1321     }
1322 }
1323 
1324 void SvtLineListBox::ImpGetLine( long nLine1, long nLine2, long nDistance,
1325                             Color aColor1, Color aColor2, Color aColorDist,
1326                             SvxBorderLineStyle nStyle, BitmapEx& rBmp )
1327 {
1328     Size aSize(getPreviewSize(*m_xControl));
1329 
1330     // SourceUnit to Twips
1331     if ( eSourceUnit == FieldUnit::POINT )
1332     {
1333         nLine1      /= 5;
1334         nLine2      /= 5;
1335         nDistance   /= 5;
1336     }
1337 
1338     // Paint the lines
1339     aSize = aVirDev->PixelToLogic( aSize );
1340     long nPix = aVirDev->PixelToLogic( Size( 0, 1 ) ).Height();
1341     sal_uInt32 n1 = nLine1;
1342     sal_uInt32 n2 = nLine2;
1343     long nDist  = nDistance;
1344     n1 += nPix-1;
1345     n1 -= n1%nPix;
1346     if ( n2 )
1347     {
1348         nDist += nPix-1;
1349         nDist -= nDist%nPix;
1350         n2    += nPix-1;
1351         n2    -= n2%nPix;
1352     }
1353     long nVirHeight = n1+nDist+n2;
1354     if ( nVirHeight > aSize.Height() )
1355         aSize.setHeight( nVirHeight );
1356     // negative width should not be drawn
1357     if ( aSize.Width() <= 0 )
1358         return;
1359 
1360     Size aVirSize = aVirDev->LogicToPixel( aSize );
1361     if ( aVirDev->GetOutputSizePixel() != aVirSize )
1362         aVirDev->SetOutputSizePixel( aVirSize );
1363     aVirDev->SetFillColor( aColorDist );
1364     aVirDev->DrawRect( tools::Rectangle( Point(), aSize ) );
1365 
1366     aVirDev->SetFillColor( aColor1 );
1367 
1368     double y1 = double( n1 ) / 2;
1369     svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y1 ), basegfx::B2DPoint( aSize.Width( ), y1 ), n1, nStyle );
1370 
1371     if ( n2 )
1372     {
1373         double y2 =  n1 + nDist + double( n2 ) / 2;
1374         aVirDev->SetFillColor( aColor2 );
1375         svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y2 ), basegfx::B2DPoint( aSize.Width(), y2 ), n2, SvxBorderLineStyle::SOLID );
1376     }
1377     rBmp = aVirDev->GetBitmapEx( Point(), Size( aSize.Width(), n1+nDist+n2 ) );
1378 }
1379 
1380 namespace
1381 {
1382     OUString GetLineStyleName(SvxBorderLineStyle eStyle)
1383     {
1384         OUString sRet;
1385         for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i)
1386         {
1387             if (eStyle == RID_SVXSTR_BORDERLINE[i].second)
1388             {
1389                 sRet = SvtResId(RID_SVXSTR_BORDERLINE[i].first);
1390                 break;
1391             }
1392         }
1393         return sRet;
1394     }
1395 }
1396 
1397 SvtLineListBox::SvtLineListBox(std::unique_ptr<weld::MenuButton> pControl)
1398     : m_xControl(std::move(pControl))
1399     , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/linewindow.ui"))
1400     , m_xTopLevel(m_xBuilder->weld_widget("line_popup_window"))
1401     , m_xNoneButton(m_xBuilder->weld_button("none_line_button"))
1402     , m_xLineSet(new ValueSet(nullptr))
1403     , m_xLineSetWin(new weld::CustomWeld(*m_xBuilder, "lineset", *m_xLineSet))
1404     , m_nWidth( 5 )
1405     , aVirDev(VclPtr<VirtualDevice>::Create())
1406     , aColor(COL_BLACK)
1407     , maPaintCol(COL_BLACK)
1408 {
1409     const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
1410     m_xLineSet->SetStyle(WinBits(WB_FLATVALUESET | WB_NO_DIRECTSELECT | WB_TABSTOP));
1411     m_xLineSet->SetItemHeight(rStyleSettings.GetListBoxPreviewDefaultPixelSize().Height() + 1);
1412     m_xLineSet->SetColCount(1);
1413     m_xLineSet->SetSelectHdl(LINK(this, SvtLineListBox, ValueSelectHdl));
1414 
1415     m_xNoneButton->connect_clicked(LINK(this, SvtLineListBox, NoneHdl));
1416 
1417     m_xTopLevel->connect_focus_in(LINK(this, SvtLineListBox, FocusHdl));
1418     m_xControl->set_popover(m_xTopLevel.get());
1419     m_xControl->connect_toggled(LINK(this, SvtLineListBox, ToggleHdl));
1420 
1421     // lock size to these maxes height/width so it doesn't jump around in size
1422     m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE));
1423     Size aNonePrefSize = m_xControl->get_preferred_size();
1424     m_xControl->set_label("");
1425     aVirDev->SetOutputSizePixel(getPreviewSize(*m_xControl));
1426     m_xControl->set_image(aVirDev);
1427     Size aSolidPrefSize = m_xControl->get_preferred_size();
1428     m_xControl->set_size_request(std::max(aNonePrefSize.Width(), aSolidPrefSize.Width()),
1429                                  std::max(aNonePrefSize.Height(), aSolidPrefSize.Height()));
1430 
1431     eSourceUnit = FieldUnit::POINT;
1432 
1433     aVirDev->SetLineColor();
1434     aVirDev->SetMapMode(MapMode(MapUnit::MapTwip));
1435 
1436     UpdatePaintLineColor();
1437 }
1438 
1439 IMPL_LINK_NOARG(SvtLineListBox, FocusHdl, weld::Widget&, void)
1440 {
1441     if (GetSelectEntryStyle() == SvxBorderLineStyle::NONE)
1442         m_xNoneButton->grab_focus();
1443     else
1444         m_xLineSet->GrabFocus();
1445 }
1446 
1447 IMPL_LINK(SvtLineListBox, ToggleHdl, weld::ToggleButton&, rButton, void)
1448 {
1449     if (rButton.get_active())
1450         FocusHdl(*m_xTopLevel);
1451 }
1452 
1453 IMPL_LINK_NOARG(SvtLineListBox, NoneHdl, weld::Button&, void)
1454 {
1455     SelectEntry(SvxBorderLineStyle::NONE);
1456     ValueSelectHdl(nullptr);
1457 }
1458 
1459 SvtLineListBox::~SvtLineListBox()
1460 {
1461 }
1462 
1463 sal_Int32 SvtLineListBox::GetStylePos( sal_Int32 nListPos )
1464 {
1465     sal_Int32 nPos = -1;
1466     --nListPos;
1467 
1468     sal_Int32 n = 0;
1469     size_t i = 0;
1470     size_t nCount = m_vLineList.size();
1471     while ( nPos == -1 && i < nCount )
1472     {
1473         if ( nListPos == n )
1474             nPos = static_cast<sal_Int32>(i);
1475         n++;
1476         i++;
1477     }
1478 
1479     return nPos;
1480 }
1481 
1482 void SvtLineListBox::SelectEntry(SvxBorderLineStyle nStyle)
1483 {
1484     if (nStyle == SvxBorderLineStyle::NONE)
1485         m_xLineSet->SetNoSelection();
1486     else
1487         m_xLineSet->SelectItem(static_cast<sal_Int16>(nStyle) + 1);
1488     UpdatePreview();
1489 }
1490 
1491 void SvtLineListBox::InsertEntry(
1492     const BorderWidthImpl& rWidthImpl, SvxBorderLineStyle nStyle, long nMinWidth,
1493     ColorFunc pColor1Fn, ColorFunc pColor2Fn, ColorDistFunc pColorDistFn )
1494 {
1495     m_vLineList.emplace_back(new ImpLineListData(
1496         rWidthImpl, nStyle, nMinWidth, pColor1Fn, pColor2Fn, pColorDistFn));
1497 }
1498 
1499 void SvtLineListBox::UpdatePaintLineColor()
1500 {
1501     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
1502     Color aNewCol(rSettings.GetWindowColor().IsDark() ? rSettings.GetLabelTextColor() : aColor);
1503 
1504     bool bRet = aNewCol != maPaintCol;
1505 
1506     if( bRet )
1507         maPaintCol = aNewCol;
1508 }
1509 
1510 void SvtLineListBox::UpdateEntries()
1511 {
1512     UpdatePaintLineColor( );
1513 
1514     SvxBorderLineStyle eSelected = GetSelectEntryStyle();
1515 
1516     // Remove the old entries
1517     m_xLineSet->Clear();
1518 
1519     // Add the new entries based on the defined width
1520 
1521     sal_uInt16 n = 0;
1522     sal_uInt16 nCount = m_vLineList.size( );
1523     while ( n < nCount )
1524     {
1525         auto& pData = m_vLineList[ n ];
1526         BitmapEx aBmp;
1527         ImpGetLine( pData->GetLine1ForWidth( m_nWidth ),
1528                 pData->GetLine2ForWidth( m_nWidth ),
1529                 pData->GetDistForWidth( m_nWidth ),
1530                 GetColorLine1(m_xLineSet->GetItemCount()),
1531                 GetColorLine2(m_xLineSet->GetItemCount()),
1532                 GetColorDist(m_xLineSet->GetItemCount()),
1533                 pData->GetStyle(), aBmp );
1534         sal_Int16 nItemId = static_cast<sal_Int16>(pData->GetStyle()) + 1;
1535         m_xLineSet->InsertItem(nItemId, Image(aBmp), GetLineStyleName(pData->GetStyle()));
1536         if (pData->GetStyle() == eSelected)
1537             m_xLineSet->SelectItem(nItemId);
1538         n++;
1539     }
1540 
1541     m_xLineSet->SetOptimalSize();
1542 }
1543 
1544 Color SvtLineListBox::GetColorLine1( sal_Int32 nPos )
1545 {
1546     sal_Int32 nStyle = GetStylePos( nPos );
1547     if (nStyle == -1)
1548         return GetPaintColor( );
1549     auto& pData = m_vLineList[ nStyle ];
1550     return pData->GetColorLine1( GetColor( ) );
1551 }
1552 
1553 Color SvtLineListBox::GetColorLine2( sal_Int32 nPos )
1554 {
1555     sal_Int32 nStyle = GetStylePos(nPos);
1556     if (nStyle == -1)
1557         return GetPaintColor( );
1558     auto& pData = m_vLineList[ nStyle ];
1559     return pData->GetColorLine2( GetColor( ) );
1560 }
1561 
1562 Color SvtLineListBox::GetColorDist( sal_Int32 nPos )
1563 {
1564     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
1565     Color rResult = rSettings.GetFieldColor();
1566 
1567     sal_Int32 nStyle = GetStylePos( nPos );
1568     if (nStyle == -1)
1569         return rResult;
1570     auto& pData = m_vLineList[ nStyle ];
1571     return pData->GetColorDist( GetColor( ), rResult );
1572 }
1573 
1574 IMPL_LINK_NOARG(SvtLineListBox, ValueSelectHdl, ValueSet*, void)
1575 {
1576     maSelectHdl.Call(*this);
1577     UpdatePreview();
1578     if (m_xControl->get_active())
1579         m_xControl->set_active(false);
1580 }
1581 
1582 void SvtLineListBox::UpdatePreview()
1583 {
1584     SvxBorderLineStyle eStyle = GetSelectEntryStyle();
1585     for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i)
1586     {
1587         if (eStyle == RID_SVXSTR_BORDERLINE[i].second)
1588         {
1589             m_xControl->set_label(SvtResId(RID_SVXSTR_BORDERLINE[i].first));
1590             break;
1591         }
1592     }
1593 
1594     if (eStyle == SvxBorderLineStyle::NONE)
1595     {
1596         m_xControl->set_image(nullptr);
1597         m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE));
1598     }
1599     else
1600     {
1601         Image aImage(m_xLineSet->GetItemImage(m_xLineSet->GetSelectedItemId()));
1602         m_xControl->set_label("");
1603         const auto nPos = (aVirDev->GetOutputSizePixel().Height() - aImage.GetSizePixel().Height()) / 2;
1604         aVirDev->Push(PushFlags::MAPMODE);
1605         aVirDev->SetMapMode(MapMode(MapUnit::MapPixel));
1606         aVirDev->Erase();
1607         aVirDev->DrawImage(Point(0, nPos), aImage);
1608         m_xControl->set_image(aVirDev.get());
1609         aVirDev->Pop();
1610     }
1611 }
1612 
1613 SvtCalendarBox::SvtCalendarBox(std::unique_ptr<weld::MenuButton> pControl)
1614     : m_xControl(std::move(pControl))
1615     , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/datewindow.ui"))
1616     , m_xTopLevel(m_xBuilder->weld_widget("date_popup_window"))
1617     , m_xCalendar(m_xBuilder->weld_calendar("date"))
1618 {
1619     m_xControl->set_popover(m_xTopLevel.get());
1620     m_xCalendar->connect_selected(LINK(this, SvtCalendarBox, SelectHdl));
1621     m_xCalendar->connect_activated(LINK(this, SvtCalendarBox, ActivateHdl));
1622 }
1623 
1624 void SvtCalendarBox::set_date(const Date& rDate)
1625 {
1626     m_xCalendar->set_date(rDate);
1627     set_label_from_date();
1628 }
1629 
1630 void SvtCalendarBox::set_label_from_date()
1631 {
1632     const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
1633     m_xControl->set_label(rLocaleData.getDate(m_xCalendar->get_date()));
1634 }
1635 
1636 IMPL_LINK_NOARG(SvtCalendarBox, SelectHdl, weld::Calendar&, void)
1637 {
1638     set_label_from_date();
1639     m_aSelectHdl.Call(*this);
1640 }
1641 
1642 IMPL_LINK_NOARG(SvtCalendarBox, ActivateHdl, weld::Calendar&, void)
1643 {
1644     if (m_xControl->get_active())
1645         m_xControl->set_active(false);
1646     m_aActivatedHdl.Call(*this);
1647 }
1648 
1649 SvtCalendarBox::~SvtCalendarBox()
1650 {
1651 }
1652 
1653 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1654